Skip to content

Multi-Repo Strategy

Why Nine Repositories (And Growing)

The VelaPay workspace has evolved from 5 repos to 11 across 8 milestones:

vela-labs/
├── vela-protocol/   # Anchor/Rust on-chain program (dual: protocol + transfer hook)
├── vela-sdk/        # TypeScript SDK + CLI + webhook event schemas
├── vela-dashboard/  # Merchant dashboard + backend API
├── vela-admin/      # Internal admin ops dashboard
├── vela-checkout/   # Hosted checkout at pay.velapay.com
├── vela-portal/     # Subscriber portal at portal.velapay.com
├── vela-widget/     # Embedded checkout for merchant pages
├── vela-webhook/    # Webhook event SDK (@vela/webhook)
├── vela-web/        # Landing page at velapay.com
├── vela-docs/       # Developer documentation at docs.velapay.com
└── vela-synthetic/  # E2E cron worker for staging validation

Evolution Timeline

PhaseRepo CountWhat Was AddedWhy
v1.05vela-protocol, vela-sdk, vela-dashboard, vela-web, vela-docsInitial workspace — the billing primitive works
v1.16+ vela-widgetMerchants needed embeddable checkout on their own pages
v1.27+ vela-adminProtocol team needed internal ops dashboard
v1.59+ vela-checkout, vela-portalHosted billing surfaces for payment links and subscriber self-service
v1.811+ vela-webhook, vela-syntheticTyped webhook delivery and continuous staging validation

Each new repo was added when a distinct runtime concern, auth model, or deploy lifecycle emerged that couldn't be cleanly contained in an existing repo.

Why Not Monorepo

A monorepo would add complexity without benefit for this project:

  1. Different deploy targets — Protocol deploys to Solana, dashboard to Railway, everything else to Cloudflare Workers. Monorepo CI would need 5+ deploy pipelines.
  2. Different toolchains — Rust for protocol, TypeScript for everything else. Shared tooling is limited to git.
  3. Different update cadences — SDK needs frequent small updates, protocol needs careful versioning. Monorepo would force coordinated releases.
  4. Different testing strategiescargo test with LiteSVM for protocol, bun test for TypeScript repos. Shared test infrastructure is minimal.
  5. Different security models — Admin has wallet-gated SIWS auth, dashboard has email-primary auth, checkout/portal are public-facing with magic-link auth. Monorepo makes auth boundary management harder.
  6. Cleaner ownership — Each team (or agent session) works in their own repo without cross-contamination.

CI/CD Strategy Per Repo

RepoCI PipelineCD TargetTrigger
vela-protocolcargo test + anchor buildSolana devnet (manual deploy)Push to master
vela-sdkbun test + tsc type checknpm publish (@vela/sdk)Version tag
vela-dashboardbun test + Docker buildRailway (SSR) + Cloudflare (Worker, D1)Push to master
vela-adminbun test + buildCloudflare WorkersPush to master
vela-checkoutbun test + buildCloudflare WorkersPush to master
vela-portalbun test + buildCloudflare WorkersPush to master
vela-widgetbun test + buildCDN (Cloudflare Workers static)Version tag
vela-webhookbun test + schema-diff checknpm publish (@vela/webhook)Version tag
vela-webbun test + Astro buildCloudflare Workers (static)Push to master
vela-docsbun test + Astro buildCloudflare Workers (static)Push to master
vela-syntheticbun test + buildCloudflare Workers (cron)Push to master

Special CI Patterns

  • Schema-diff check (vela-webhook): CI enforces additive-only event evolution. New event types are allowed; removing or changing existing events fails the build.
  • Staging E2E (vela-synthetic): 15-minute cron against devnet staging. Pages on failure.
  • Playwright E2E (vela-checkout, vela-portal): Full browser tests with Mailpit for email-dependent flows.

Cross-Repo Update Protocol

Protocol → SDK → Consumers

When vela-protocol updates, a specific chain of updates is required:

1. vela-protocol: Make changes, run cargo test, anchor build
       ↓ IDL output
2. vela-sdk: Copy new IDL, update instruction builders, run bun test
       ↓ published to npm
3. vela-dashboard: Update @vela/sdk dependency, adapt to API changes
   vela-checkout: Update @vela/sdk dependency, adapt to API changes
   vela-portal: Update @vela/sdk dependency, adapt to API changes
   vela-widget: Update @vela/sdk dependency, adapt to API changes
   vela-webhook: Update @vela/sdk dependency, adapt to event schemas

Version Compatibility Rules

  • Protocol ↔ SDK: Must match exactly. SDK encodes program IDL, instruction discriminators, and account layouts.
  • SDK → Consumers: Semantic versioning. Breaking SDK changes require major version bump.
  • Consumers → Each Other: No direct dependencies. Communication via shared D1 database and on-chain state.

Coordinated Change Protocol

  1. Update protocol first (source of truth).
  2. Update SDK second (encoding layer).
  3. Update consumers last (product surfaces).
  4. Use conventional commits for tracking (feat(protocol):, feat(sdk):, feat(dashboard):).
  5. Each step is a separate PR and deploy.

Why vela-checkout and vela-portal Are Separate

From the Dashboard

vela-checkout and vela-portal were extracted from vela-dashboard in v1.5 because they have fundamentally different runtime concerns:

Dimensionvela-dashboardvela-checkoutvela-portal
Auth modelEmail-primary + org membershipPublic (Turnstile-gated)Magic-link + SIWS
AudienceMerchants (authenticated)Subscribers (anonymous)Subscribers (authenticated)
RuntimeRailway SSR + CF Worker APICF Worker onlyCF Worker only
Deploy cadenceChanges with merchant featuresChanges with checkout UXChanges with portal features
Security boundaryInternal — merchant dataPublic — payment flowPublic — subscription management
Domainapp.velapay.compay.velapay.comportal.velapay.com

Why Not Merge Them

Merging checkout or portal into the dashboard would:

  • Expose merchant auth boundaries to public traffic.
  • Force a single deploy cycle for merchant features and subscriber UX.
  • Create a larger attack surface (checkout doesn't need merchant auth, portal doesn't need merchant data).
  • Make independent scaling harder (checkout has bursty traffic during payment campaigns).

How They Communicate

Checkout and portal share patterns with the dashboard:

  • Checkout session creation: Both vela-checkout and vela-portal (for plan switching) create checkout sessions via a shared pattern that writes to D1.
  • Public billing apps proxy to dashboard internal routes: Some API endpoints are shared, with the dashboard Worker handling the backend logic.
  • D1 database: Shared database for checkout sessions, subscription state, and billing records.

Trade-offs Analysis

What Multi-Repo Gives Us

BenefitImpact
Independent deploysProtocol can be redeployed without touching the dashboard. Checkout can be updated without touching the widget.
Independent versioningSDK can publish a patch without coordinating with protocol. Widget can version independently from docs.
Clearer ownershipEach repo has a clear owner, clear test suite, clear deploy pipeline.
Smaller clone/fetch sizeOnly clone what you need. Protocol developers don't need checkout code.
Optimal toolchain per repoRust for protocol, TypeScript+Hono for Workers, Astro for docs. No compromise.
Auth boundary isolationEach repo enforces its own auth model without cross-contamination.

What Multi-Repo Costs

CostMitigation
No atomic commits across reposConventional commits + update protocol. Accept that cross-repo changes require coordination.
Shared config managed separately@biomejs/biome config is similar but not shared. Acceptable divergence.
Cross-repo refactoring is harderProtocol versioning (v1.7 reserved space) makes additive changes the default. Breaking changes are rare and planned.
More CI pipelines to maintainEach pipeline is simple (build + test + deploy). Complexity per pipeline is low.
Dependency drift across reposWorkspace-level bun.lock anchors versions. SDK version pinning prevents drift.

When Monorepo Would Be Better

If the team grows to 5+ engineers working on the same product surfaces, monorepo benefits (atomic commits, shared CI, unified versioning) might outweigh the deploy isolation benefits. For the current team size and architecture, multi-repo is the right choice.

GitHub Organization Structure

All repos live under the GitHub organization:

github.com/vela-labs/
├── vela-protocol        # Anchor/Rust on-chain program
├── vela-sdk             # TypeScript SDK + CLI
├── vela-dashboard       # Merchant dashboard
├── vela-admin           # Internal admin dashboard
├── vela-checkout        # Hosted checkout
├── vela-portal          # Subscriber portal
├── vela-widget          # Embedded checkout
├── vela-webhook         # Webhook event SDK
├── vela-web             # Landing page
├── vela-docs            # Developer documentation
├── vela-synthetic       # E2E cron worker
└── (internal-wiki)      # Internal wiki (separate repo or workspace directory)

Branch Strategy

  • master: Default branch for all repos. Protected by CI.
  • Feature branches: Used during development, merged via PR.
  • No release branches: Continuous deployment from master.

Workspace-Level Files

The workspace root (vela-labs/) contains:

  • .planning/: Authoritative project state (requirements, roadmap, state).
  • internal-wiki/: This wiki.
  • Root strategy docs (01-vela-labs-brand.md through 10-future-vision-layer3-and-beyond.md): Founding context.
  • CLAUDE.md / AGENTS.md: AI agent instructions shared across repos.
  • docker-compose.staging.yml: Staging environment for E2E tests.

Shared Dependencies

All TypeScript repos share these dependencies (managed separately per repo):

PackageVersionPurpose
typescript5.8+Type checking
@biomejs/biomelatestLinting + formatting
wrangler4.78.xCloudflare CLI (Workers repos)
@vela/sdklatestProtocol client (all product repos)

No shared package.json — each repo manages its own dependencies independently.

.claude Directory

Each repo has a .claude/ directory with:

  • Project context (CLAUDE.md with repo-specific instructions).
  • Agent instructions for automated development.
  • Repository-specific conventions and constraints.

This enables AI agents to work effectively in any repo without cross-repo context.

Internal knowledge base for the Vela Labs workspace.