|
| 1 | +# Offworld Architecture Review (2026-02-02) |
| 2 | + |
| 3 | +Scope: CLI build/distribution, SDK packaging, Convex type flow, workspace release pipeline. |
| 4 | + |
| 5 | +## Executive Summary |
| 6 | + |
| 7 | +The core architecture is strong, but the current build and distribution path still has avoidable indirection and reliability hazards. The most impactful simplifications are: (1) remove `@offworld/backend-api` by moving Convex generated outputs into the SDK build pipeline, (2) split optional-heavy features (Convex sync + AI) into subpath exports and dynamic imports, and (3) tighten release safeguards (version lockstep + publish order) so the CLI and SDK never drift. These changes are structural and preserve existing CLI UX. |
| 8 | + |
| 9 | +## Current State (Verified) |
| 10 | + |
| 11 | +### CLI Build & Distribution |
| 12 | + |
| 13 | +- The CLI is bundled with `tsdown` into ESM output with two entrypoints: `src/cli.ts` (bin) and `src/index.ts` (library). See [apps/cli/tsdown.config.ts](file:///Users/oscargabriel/Developer/projects/offworld/apps/cli/tsdown.config.ts). |
| 14 | +- The published npm package exposes `dist/cli.mjs` as the executable and `dist/index.mjs` for library exports. See [apps/cli/package.json](file:///Users/oscargabriel/Developer/projects/offworld/apps/cli/package.json). |
| 15 | +- Release binaries are compiled from the ESM bundle using `bun build --compile` in GitHub Actions. See the build job in [release.yml](file:///Users/oscargabriel/Developer/projects/offworld/.github/workflows/release.yml). |
| 16 | +- The installer downloads prebuilt binaries, verifies checksums, and installs a single `ow` binary. See [install](file:///Users/oscargabriel/Developer/projects/offworld/install). |
| 17 | + |
| 18 | +### SDK Build & API Surface |
| 19 | + |
| 20 | +- The SDK currently builds a single entrypoint (`src/index.ts`) with `tsdown` to `dist/index.mjs`. See [packages/sdk/tsdown.config.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/sdk/tsdown.config.ts). |
| 21 | +- The SDK default export surface includes sync and AI exports directly, meaning `convex` and `@opencode-ai/sdk` are pulled via the main entrypoint. See [packages/sdk/src/index.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/sdk/src/index.ts). |
| 22 | +- AI access already uses dynamic import internally (`@opencode-ai/sdk`). See [packages/sdk/src/ai/opencode.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/sdk/src/ai/opencode.ts). |
| 23 | + |
| 24 | +### Convex Generated Types |
| 25 | + |
| 26 | +- Convex generates API stubs under `packages/backend/convex/_generated`. See [packages/backend/convex/_generated/api.d.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/backend/convex/_generated/api.d.ts). |
| 27 | +- `@offworld/backend-api` is a packaging shim that copies those generated files into `dist/_generated` and exposes `./api` and `./server`. See [packages/backend-api/package.json](file:///Users/oscargabriel/Developer/projects/offworld/packages/backend-api/package.json) and [packages/backend-api/scripts/copy-generated.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/backend-api/scripts/copy-generated.ts). |
| 28 | +- SDK sync code imports the Convex API from that shim, creating a build and publish dependency chain. See [packages/sdk/src/sync.ts](file:///Users/oscargabriel/Developer/projects/offworld/packages/sdk/src/sync.ts). |
| 29 | + |
| 30 | +### Release Pipeline and Publishing |
| 31 | + |
| 32 | +- Release builds generate Convex code, run `turbo build`, compile binaries, then publish packages in a fixed order: `@offworld/types`, `@offworld/backend-api`, `@offworld/sdk`, `offworld`. See [release.yml](file:///Users/oscargabriel/Developer/projects/offworld/.github/workflows/release.yml). |
| 33 | +- The version bump script enforces a lockstep version across CLI, SDK, types, and backend-api, and regenerates build output. See [scripts/bump-version.ts](file:///Users/oscargabriel/Developer/projects/offworld/scripts/bump-version.ts). |
| 34 | +- There is no `scripts/verify-versions.ts` in the current tree, despite being referenced in prior change logs. |
| 35 | + |
| 36 | +## Pain Points |
| 37 | + |
| 38 | +1. **Backend API indirection**: The `@offworld/backend-api` package exists only to copy Convex generated files. This adds a build step and a release dependency without adding behavior. |
| 39 | +2. **Heavy dependencies in the default SDK entrypoint**: `convex` and AI-related code are available on the main entrypoint, which means consumers pay for them even if they only use local repo management. |
| 40 | +3. **Release fragility**: The release pipeline depends on build/publish order and the presence of the backend-api shim. Any mismatch between versions or missing codegen output breaks publishing. |
| 41 | +4. **Public vs internal API**: The SDK entrypoint currently acts as a catch-all export surface, which makes it hard to draw a stable boundary between “public API” and “CLI internals.” |
| 42 | + |
| 43 | +## Recommended Approach (Simplify + Improve Reliability) |
| 44 | + |
| 45 | +### 1) Merge Convex Type Surface Into the SDK |
| 46 | + |
| 47 | +**Goal:** Remove `@offworld/backend-api` and expose generated Convex types directly from `@offworld/sdk`. |
| 48 | + |
| 49 | +**Approach:** |
| 50 | + |
| 51 | +- Add an SDK build step to copy `packages/backend/convex/_generated` into `packages/sdk/dist/convex/_generated` after `tsdown` completes. |
| 52 | +- Add explicit subpath exports for Convex types under the SDK package: |
| 53 | + |
| 54 | +```json |
| 55 | +"./convex/api": { "types": "./dist/convex/_generated/api.d.ts", "import": "./dist/convex/_generated/api.js" }, |
| 56 | +"./convex/server": { "types": "./dist/convex/_generated/server.d.ts", "import": "./dist/convex/_generated/server.js" } |
| 57 | +``` |
| 58 | + |
| 59 | +- Update SDK sync modules to import from `@offworld/sdk/convex/api` instead of `@offworld/backend-api`. |
| 60 | +- Remove `packages/backend-api` and eliminate it from publish workflows and version scripts. |
| 61 | + |
| 62 | +This directly removes a full build stage and package, while keeping Convex types accessible in a standard, explicit location. |
| 63 | + |
| 64 | +### 2) Split Optional Dependencies Behind Subpath Exports |
| 65 | + |
| 66 | +**Goal:** Avoid loading Convex + AI in the default SDK entrypoint. |
| 67 | + |
| 68 | +**Approach:** |
| 69 | + |
| 70 | +- Move sync exports under `@offworld/sdk/sync` and AI exports under `@offworld/sdk/ai`. |
| 71 | +- Keep the main SDK entrypoint (`@offworld/sdk`) limited to core local functionality: config, clone, map, repo management, reference install, etc. |
| 72 | +- Make `convex` an optional peer dependency and dynamically import it in sync client creation, throwing a clear `SyncUnavailableError` when missing. |
| 73 | + |
| 74 | +This reduces default bundle size and makes SDK behavior more predictable for offline/local-only users. |
| 75 | + |
| 76 | +### 3) Define Public vs Internal SDK API |
| 77 | + |
| 78 | +**Goal:** Separate stable public API from CLI-internal utilities. |
| 79 | + |
| 80 | +**Approach:** |
| 81 | + |
| 82 | +- Introduce `public.ts` (curated export list) and make `src/index.ts` re-export only that. |
| 83 | +- Add `internal.ts` and a `@offworld/sdk/internal` export for CLI-only helpers. |
| 84 | + |
| 85 | +This reduces accidental dependency on internal APIs and gives room to evolve CLI internals without breaking external consumers. |
| 86 | + |
| 87 | +### 4) Harden Release Safeguards |
| 88 | + |
| 89 | +**Goal:** Prevent version drift and reduce publishing breakage. |
| 90 | + |
| 91 | +**Approach:** |
| 92 | + |
| 93 | +- Add a `scripts/verify-versions.ts` that checks versions across `apps/cli`, `packages/sdk`, and `packages/types` and fail CI if mismatched. |
| 94 | +- Run it before publish in CI and/or as `prepublishOnly` for packages. |
| 95 | +- Remove backend-api from the publish order once it is deleted. |
| 96 | + |
| 97 | +## Impact on CLI Bundling & Distribution |
| 98 | + |
| 99 | +- The CLI will continue to build as a single ESM bundle via `tsdown` and get compiled to a static binary via Bun in CI. This remains unchanged and reliable. |
| 100 | +- After the SDK split, the CLI should import sync and AI functionality from `@offworld/sdk/sync` and `@offworld/sdk/ai` (or `@offworld/sdk/internal`), preventing unwanted dependency loading on `@offworld/sdk` consumers. |
| 101 | +- The installer and release binary pipeline are already robust; the primary risk is ensuring the SDK build produces Convex stubs before CLI builds in CI. With an SDK copy step in its `build` script, the `turbo` build graph will handle ordering. |
| 102 | + |
| 103 | +## Suggested Implementation Order |
| 104 | + |
| 105 | +1. Merge backend-api into SDK with Convex subpath exports. |
| 106 | +2. Move sync + AI behind subpath exports; optional Convex peer dependency. |
| 107 | +3. Create `public.ts` + `internal.ts` and narrow the default SDK entrypoint. |
| 108 | +4. Add `verify-versions` to CI and prepublish tasks. |
| 109 | + |
| 110 | +## Definition of Done |
| 111 | + |
| 112 | +- `@offworld/backend-api` is removed without breaking sync functionality. |
| 113 | +- SDK default entrypoint no longer requires Convex or AI dependencies. |
| 114 | +- CLI builds and release binaries remain unchanged and succeed in CI. |
| 115 | +- Package version drift is caught before publish. |
0 commit comments