@terreno/mcp 0.31.0 → 56.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +15 -16
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
- package/dist/docs/templates/tools/model.template.md +2 -4
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +1 -7
- package/dist/docs/versioned/next/how-to/README.md +2 -8
- package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
- package/dist/docs/versioned/next/implementationPlans/README.md +30 -56
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
- package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
- package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
- package/dist/docs/versioned/next/reference/README.md +1 -4
- package/dist/docs/versioned/next/reference/environment-variables.md +382 -220
- package/dist/docs/versioned/next/reference/syncdb.md +456 -0
- package/dist/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
- package/dist/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
- package/dist/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
- package/dist/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
- package/package.json +4 -5
- package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
- package/src/docs/templates/tools/model.template.md +2 -4
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +1 -7
- package/src/docs/versioned/next/how-to/README.md +2 -8
- package/src/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
- package/src/docs/versioned/next/implementationPlans/README.md +30 -56
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
- package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
- package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
- package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
- package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
- package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
- package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
- package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
- package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
- package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
- package/src/docs/versioned/next/reference/README.md +1 -4
- package/src/docs/versioned/next/reference/environment-variables.md +382 -220
- package/src/docs/versioned/next/reference/syncdb.md +456 -0
- package/src/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
- package/src/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
- package/src/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
- package/src/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
- package/LICENSE +0 -21
- package/dist/__tests__/bootstrap.test.d.ts +0 -2
- package/dist/__tests__/bootstrap.test.d.ts.map +0 -1
- package/dist/__tests__/bootstrap.test.js +0 -299
- package/dist/__tests__/bootstrap.test.js.map +0 -1
- package/dist/__tests__/databaseQuery.test.d.ts +0 -2
- package/dist/__tests__/databaseQuery.test.d.ts.map +0 -1
- package/dist/__tests__/databaseQuery.test.js +0 -83
- package/dist/__tests__/databaseQuery.test.js.map +0 -1
- package/dist/__tests__/docsRoot.test.d.ts +0 -2
- package/dist/__tests__/docsRoot.test.d.ts.map +0 -1
- package/dist/__tests__/docsRoot.test.js +0 -59
- package/dist/__tests__/docsRoot.test.js.map +0 -1
- package/dist/__tests__/inferPackages.test.d.ts +0 -2
- package/dist/__tests__/inferPackages.test.d.ts.map +0 -1
- package/dist/__tests__/inferPackages.test.js +0 -35
- package/dist/__tests__/inferPackages.test.js.map +0 -1
- package/dist/__tests__/preload.d.ts +0 -2
- package/dist/__tests__/preload.d.ts.map +0 -1
- package/dist/__tests__/preload.js +0 -12
- package/dist/__tests__/preload.js.map +0 -1
- package/dist/__tests__/prompts.test.d.ts +0 -2
- package/dist/__tests__/prompts.test.d.ts.map +0 -1
- package/dist/__tests__/prompts.test.js +0 -295
- package/dist/__tests__/prompts.test.js.map +0 -1
- package/dist/__tests__/resources.test.d.ts +0 -2
- package/dist/__tests__/resources.test.d.ts.map +0 -1
- package/dist/__tests__/resources.test.js +0 -352
- package/dist/__tests__/resources.test.js.map +0 -1
- package/dist/__tests__/search.test.d.ts +0 -2
- package/dist/__tests__/search.test.d.ts.map +0 -1
- package/dist/__tests__/search.test.js +0 -163
- package/dist/__tests__/search.test.js.map +0 -1
- package/dist/__tests__/tools.test.d.ts +0 -2
- package/dist/__tests__/tools.test.d.ts.map +0 -1
- package/dist/__tests__/tools.test.js +0 -540
- package/dist/__tests__/tools.test.js.map +0 -1
- package/dist/__tests__/upgradeGuide.test.d.ts +0 -2
- package/dist/__tests__/upgradeGuide.test.d.ts.map +0 -1
- package/dist/__tests__/upgradeGuide.test.js +0 -22
- package/dist/__tests__/upgradeGuide.test.js.map +0 -1
- package/dist/docs/upgrades/0.31.0.md +0 -60
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +0 -78
- package/dist/docs/versioned/next/explanation/positioning.md +0 -164
- package/dist/docs/versioned/next/explanation/repository-settings.md +0 -44
- package/dist/docs/versioned/next/explanation/roadmap-process.md +0 -330
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
- package/dist/docs/versioned/next/how-to/build-for-web.md +0 -69
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
- package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
- package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
- package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
- package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
- package/dist/docs/versioned/next/reference/admin-spa.md +0 -130
- package/dist/docs/versioned/next/reference/ai.md +0 -519
- package/dist/docs/versioned/next/reference/test.md +0 -108
- package/dist/docs/versioned/next/roadmap.md +0 -9
- package/dist/docs/versioned/next/tasks/README.md +0 -42
- package/dist/docs/versioned/next/tasks/billing-stripe.md +0 -75
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +0 -77
- package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
- package/dist/docs/versioned/next/tasks/infra-mcp.md +0 -43
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +0 -42
- package/dist/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
- package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
- package/src/docs/upgrades/0.31.0.md +0 -60
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
- package/src/docs/versioned/next/explanation/deployment-baseline.md +0 -78
- package/src/docs/versioned/next/explanation/positioning.md +0 -164
- package/src/docs/versioned/next/explanation/repository-settings.md +0 -44
- package/src/docs/versioned/next/explanation/roadmap-process.md +0 -330
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
- package/src/docs/versioned/next/how-to/build-for-web.md +0 -69
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
- package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
- package/src/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
- package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
- package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
- package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
- package/src/docs/versioned/next/reference/admin-spa.md +0 -130
- package/src/docs/versioned/next/reference/ai.md +0 -519
- package/src/docs/versioned/next/reference/test.md +0 -108
- package/src/docs/versioned/next/roadmap.md +0 -9
- package/src/docs/versioned/next/tasks/README.md +0 -42
- package/src/docs/versioned/next/tasks/billing-stripe.md +0 -75
- package/src/docs/versioned/next/tasks/comms-abstraction.md +0 -77
- package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
- package/src/docs/versioned/next/tasks/infra-mcp.md +0 -43
- package/src/docs/versioned/next/tasks/native-module-baseline.md +0 -42
- package/src/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
- package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Implementation Plan: @terreno/syncdb-codegen
|
|
2
|
+
|
|
3
|
+
**Status:** Draft
|
|
4
|
+
**Branch:** cursor/ip-syncdb-codegen-41d6
|
|
5
|
+
**Owner:**
|
|
6
|
+
**Created:** 2026-07-08
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
|
|
10
|
+
Ship `@terreno/syncdb-codegen`: a small CLI (runnable as an npm bin under bun and buildable as a standalone compiled bun binary) that reads a backend OpenAPI spec and generates a static, typed set of syncdb hooks — the local-first analogue of the existing `@rtk-query/codegen-openapi` flow (`bun run sdk` → `store/openApiSdk.ts`).
|
|
11
|
+
|
|
12
|
+
Today syncdb consumers hand-write both the entity types and the collection strings:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// example-frontend/components/SyncTodosScreen.tsx — all manual today
|
|
16
|
+
interface SyncTodo { _id: string; title?: string; completed?: boolean; created?: string; }
|
|
17
|
+
const todos = useQuery<SyncTodo>("todos", {sort: sortByCreatedDesc});
|
|
18
|
+
const {update, remove} = useMutate("todos"); // update/remove payloads are Record<string, unknown>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After this IP, a generated `store/syncDbSdk.ts` provides one hook per operation with the exact names RTK codegen would produce for the same routes — `useGetTodosQuery` (list query), `useGetTodosByIdQuery` (read), `usePostTodosMutation` (create), `usePatchTodosByIdMutation` (update/patch), `useDeleteTodosByIdMutation` (delete) — with payload types derived from the same OpenAPI schemas RTK codegen uses (`Todo`, `CreateTodoBody`, `UpdateTodoBody`), plus a `SYNC_COLLECTIONS` constant that feeds `createSyncDb`. Migrating a screen from RTK to syncdb becomes (mostly) an import-path change. Custom hooks are supported the same way RTK supports custom endpoints: the generated file is never edited, and consumers extend it in a sibling file using the same public factory the generated code uses.
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Covering the whole syncdb surface. Only the five CRUD operations (list query, read, create, update/patch, delete) get generated per-collection hooks. `useSyncStatus`, `useConflicts`, `useSyncDbClient`, and `SyncDbProvider` are global (not per-collection) and stay as direct imports from `@terreno/syncdb/react`.
|
|
26
|
+
- Full RTK return-shape emulation. Hook **names and call sites** match RTK; return shapes are syncdb-native where the semantics differ (no `isLoading`/`error` async state on mutations — writes are synchronous local applies plus a durable outbox; sync state lives in `useSyncStatus`). The mapping is documented in the generated file header and the migration guide.
|
|
27
|
+
- Runtime schema validation of mutation payloads (types are compile-time only, matching RTK codegen).
|
|
28
|
+
- Publishing per-platform compiled binaries to npm or GitHub releases (the compiled binary is a local/CI build target for now; `@terreno/syncdb` itself is not in `publish-on-tag.yml` yet either).
|
|
29
|
+
- Watch mode, incremental generation, or multi-output-file support.
|
|
30
|
+
- Generating Redux integration or migrating non-synced endpoints — per `docs/how-to/migrate-rtk-to-syncdb.md`, custom routes stay on RTK.
|
|
31
|
+
|
|
32
|
+
## Decisions
|
|
33
|
+
|
|
34
|
+
Options were considered where the design was genuinely open; each row records the recommendation this plan is built on. Rejected alternatives are listed so review can overrule them cheaply.
|
|
35
|
+
|
|
36
|
+
| Question | Decision |
|
|
37
|
+
|----------|----------|
|
|
38
|
+
| **How does codegen know which collections are synced?** OpenAPI does not mark sync today; the server-side `syncRegistry` (`api/src/sync/registry.ts`) is in-memory only. Options: (a) emit an `x-terreno-sync` vendor extension on the list path from `modelRouter` when `options.sync` is set; (b) require an explicit `--collections` CLI arg; (c) add a `GET /sync/config` manifest endpoint. | **(a) with (b) as override.** The extension is a ~10-line change in `@terreno/api`'s list OpenAPI middleware, keeps the spec the single input (same as RTK codegen), and can't drift from the backend. `--collections` remains as an explicit filter/override for apps that sync a subset. (c) adds server surface for no extra information and is rejected. |
|
|
39
|
+
| **How is "retries vs not" expressed?** RTK disables retries per-endpoint via `extraOptions: {maxRetries: 0}` (see `rtk/src/authSlice.ts`). syncdb retries live in the replay engine: `MAX_ERROR_NACK_ATTEMPTS = 5` with exponential backoff in `syncdb/src/sync/replayCoordinator.ts`, not configurable per mutation. Options: (a) plumb an optional per-mutation `maxAttempts` through `client.mutate` → outbox row → replay coordinator, and expose `retries` in the codegen config; (b) codegen-only option that is silently ignored until the engine supports it. | **(a).** Small, contained change: `MutateArgs.maxAttempts?`, one optional outbox cell, one `??` in the replay coordinator's error-nack branch. `retries: false` maps to `maxAttempts: 1` (fail fast, like RTK's `maxRetries: 0`), a number maps directly, and omitted keeps the engine default of 5. (b) would generate config that lies. Note syncdb mutations are idempotent via `mutationId`, so unlike RTK the motivation is fail-fast UX (surface a terminal failure immediately), not duplicate-write safety. |
|
|
40
|
+
| **Config surface: flags only, or a config file?** The binary should "take a few args", but per-collection overrides (retries, exclusions) don't map well to flags. | **Flags for the common path, optional JSON config for overrides.** `--schema`, `--out`, `--collections` cover the RTK-equivalent happy path. `--config ./syncdb-codegen.json` adds `{overrides: {<collection>: {retries?: boolean \| number}}, exportName?, sdkImportPath?}`. JSON (not TS) because a compiled bun binary cannot reliably import arbitrary consumer TypeScript at runtime; RTK's TS `ConfigFile` needs the `tsx`/`TS_NODE_PROJECT` dance we currently work around in `example-frontend/scripts/generate-sdk.ts`. |
|
|
41
|
+
| **How are TS types emitted from OpenAPI schemas?** Options: (a) a small hand-rolled emitter for the subset `mongoose-to-swagger` actually produces (objects, primitives, enums as string unions, arrays, `$ref`); (b) depend on `openapi-typescript` or oazapfts. | **(a).** The @terreno/api spec is a narrow, known dialect; a ~150-line emitter with snapshot tests keeps the binary dependency-free and small. If the emitter grows past that dialect, revisit (b). |
|
|
42
|
+
| **Where does the shared hook factory live?** Generated code and custom hooks must be built the same way (the RTK parallel: generated `injectEndpoints` and app `sdk.ts` both build on `emptySplitApi`). Options: (a) new `createCollectionHooks` export in `@terreno/syncdb/react`; (b) inline the factory into every generated file; (c) a runtime export of `@terreno/syncdb-codegen`. | **(a).** One implementation, versioned with the hooks it wraps, and consumers already depend on `@terreno/syncdb`. (b) duplicates logic into generated output; (c) would make the codegen package a runtime dependency of apps, which it should never be. |
|
|
43
|
+
| **Hook granularity and naming.** Options: (a) one grouped `useTodosMutate()` returning `{create, update, remove}`; (b) one hook per operation with RTK codegen's exact naming scheme (`use{Method}{Path}Query/Mutation`); (c) per-operation hooks with friendlier names (`useCreateTodo`). | **(b) — per-operation, RTK names.** Generated per collection: `useGetTodosQuery`, `useGetTodosByIdQuery`, `usePostTodosMutation`, `usePatchTodosByIdMutation`, `useDeleteTodosByIdMutation`. Name derivation is the same deterministic pascal-case of the route path RTK uses (no singularization heuristics). This makes RTK→syncdb migration an import swap. Two consequences are accepted: (1) name **collisions with `openApiSdk.ts`** are intentional — during migration both files export e.g. `useGetTodosQuery`, and the import path picks the data source; imports must never be mixed via a barrel that re-exports both. (2) "update" and "patch" are the **same hook**: modelRouter only exposes PATCH (merge semantics) and syncdb's update is a merge, so `usePatchTodosByIdMutation` is the update hook — there is no separate PUT/replace hook to generate. |
|
|
44
|
+
| **Mutation hook return shape.** RTK mutation hooks return `[trigger, {isLoading, error, ...}]` and triggers return promises with `.unwrap()`. syncdb writes are synchronous local applies; there is no per-call async state. | **One-element tuple `[trigger]`.** Keeps the RTK call site (`const [createTodo] = usePostTodosMutation()`) working unchanged; the trigger returns `{mutationId, id}` synchronously instead of a promise, so `.unwrap()`/`isLoading` usages are deleted during migration (the compiler flags them). Pending/conflict state is per-entity (`isPending` on reads) and global (`useSyncStatus`), not per-call. |
|
|
45
|
+
| **Distribution.** "Compiled bun binary" vs npm reality. | **Both, npm-bin first.** The package ships `"bin": {"terreno-syncdb-codegen": "./dist/cli.js"}` with a `#!/usr/bin/env bun` shebang (the `@terreno/mcp` pattern), plus a `build:binary` script running `bun build --compile src/cli.ts --outfile dist/terreno-syncdb-codegen` (the `example-backend/Dockerfile` pattern) for standalone use. Publishing platform binaries is future work. |
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
example-backend example-frontend
|
|
51
|
+
modelRouter("/todos", Todo, {sync: {...}}) bun run sync-sdk
|
|
52
|
+
└─ list OpenAPI middleware emits └─ terreno-syncdb-codegen
|
|
53
|
+
x-terreno-sync on GET /todos --schema http://localhost:4000/openapi.json
|
|
54
|
+
│ --out ./store/syncDbSdk.ts
|
|
55
|
+
▼ │
|
|
56
|
+
/openapi.json ──── fetch/read ────────────────────────┘
|
|
57
|
+
│ emits
|
|
58
|
+
▼
|
|
59
|
+
store/syncDbSdk.ts (generated, never edited)
|
|
60
|
+
├─ interface Todo / CreateTodoBody / UpdateTodoBody
|
|
61
|
+
├─ export const SYNC_COLLECTIONS = ["todos"] as const
|
|
62
|
+
└─ export const {useGetTodosQuery, useGetTodosByIdQuery,
|
|
63
|
+
usePostTodosMutation, usePatchTodosByIdMutation,
|
|
64
|
+
useDeleteTodosByIdMutation} =
|
|
65
|
+
createCollectionHooks<...>({collection: "todos"})
|
|
66
|
+
│ extended by (custom hooks)
|
|
67
|
+
▼
|
|
68
|
+
store/syncSdk.ts (hand-written, optional)
|
|
69
|
+
└─ createCollectionHooks<LocalNote>({collection: "notes"})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Three packages change:
|
|
73
|
+
|
|
74
|
+
1. **`@terreno/syncdb`** (runtime): `createCollectionHooks` factory in `src/react/`, plus per-mutation `maxAttempts` plumbing through `client.mutate` → outbox → replay coordinator.
|
|
75
|
+
2. **`@terreno/api`**: `listOpenApiMiddleware` (or the shared path-building step in `api/src/openApi.ts`) adds `"x-terreno-sync": {collection, scope}` to the list operation when `options.sync` is present.
|
|
76
|
+
3. **`@terreno/syncdb-codegen`** (new): CLI that parses the spec, discovers synced collections, and emits the SDK file.
|
|
77
|
+
|
|
78
|
+
### Runtime factory (`@terreno/syncdb/react`)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export interface CollectionHooksConfig {
|
|
82
|
+
collection: string;
|
|
83
|
+
/** false → 1 attempt (fail fast); number → max replay attempts; omitted → engine default (5). */
|
|
84
|
+
retries?: boolean | number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type MutationTrigger<TArgs> = (args: TArgs) => {mutationId: string; id: string};
|
|
88
|
+
|
|
89
|
+
export interface CollectionHooks<TData, TCreate, TUpdate> {
|
|
90
|
+
/** List query (RTK: useGet{Path}Query). */
|
|
91
|
+
useListQuery: (options?: UseQueryOptions<TData>) => {data: TData[]};
|
|
92
|
+
/** Single-entity read (RTK: useGet{Path}ByIdQuery). */
|
|
93
|
+
useReadQuery: (id: string) => UseEntityResult<TData>;
|
|
94
|
+
/** Create (RTK: usePost{Path}Mutation). */
|
|
95
|
+
useCreateMutation: () => [MutationTrigger<{data: TCreate}>];
|
|
96
|
+
/** Update via merge — patch semantics (RTK: usePatch{Path}ByIdMutation). */
|
|
97
|
+
useUpdateMutation: () => [MutationTrigger<{id: string; data: TUpdate}>];
|
|
98
|
+
/** Soft delete (RTK: useDelete{Path}ByIdMutation). */
|
|
99
|
+
useDeleteMutation: () => [MutationTrigger<{id: string}>];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const createCollectionHooks = <
|
|
103
|
+
TData = Record<string, unknown>,
|
|
104
|
+
TCreate = Record<string, unknown>,
|
|
105
|
+
TUpdate = Partial<TCreate>,
|
|
106
|
+
>(config: CollectionHooksConfig): CollectionHooks<TData, TCreate, TUpdate> => { ... };
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The factory returns **operation-named keys** (`useListQuery`, `useReadQuery`, `useCreateMutation`, `useUpdateMutation`, `useDeleteMutation`); the generated file renames them to the RTK-style collection-specific names via destructuring (`useListQuery: useGetTodosQuery`, ...). Custom hooks can keep the generic names or rename the same way. Each hook delegates to the existing `useQuery`/`useEntity`/`useMutate` internals (no listener logic duplicated) and threads `retries` into `client.mutate` as `maxAttempts`. This is the `emptySplitApi` of the syncdb world: generated code calls it, and custom hooks call it directly — that is the whole "custom hooks the same way RTK does" story.
|
|
110
|
+
|
|
111
|
+
### Generated file shape
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// store/syncDbSdk.ts — GENERATED by @terreno/syncdb-codegen. Do not edit.
|
|
115
|
+
import {createCollectionHooks} from "@terreno/syncdb/react";
|
|
116
|
+
|
|
117
|
+
export interface Todo { _id: string; title: string; completed: boolean; ownerId: string; created: string; updated: string; }
|
|
118
|
+
export interface CreateTodoBody { title: string; completed?: boolean; }
|
|
119
|
+
export type UpdateTodoBody = Partial<CreateTodoBody>;
|
|
120
|
+
|
|
121
|
+
/** Collections with sync enabled on the backend; pass to createSyncDb. */
|
|
122
|
+
export const SYNC_COLLECTIONS = ["todos"] as const;
|
|
123
|
+
|
|
124
|
+
export const {
|
|
125
|
+
useListQuery: useGetTodosQuery,
|
|
126
|
+
useReadQuery: useGetTodosByIdQuery,
|
|
127
|
+
useCreateMutation: usePostTodosMutation,
|
|
128
|
+
useUpdateMutation: usePatchTodosByIdMutation,
|
|
129
|
+
useDeleteMutation: useDeleteTodosByIdMutation,
|
|
130
|
+
} = createCollectionHooks<Todo, CreateTodoBody, UpdateTodoBody>({collection: "todos"});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Call-site comparison for migration:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// RTK // syncdb (generated)
|
|
137
|
+
const {data} = useGetTodosQuery({}); const {data} = useGetTodosQuery();
|
|
138
|
+
const {data} = useGetTodosByIdQuery({id}); const {data, isPending} = useGetTodosByIdQuery(id);
|
|
139
|
+
const [createTodo] = usePostTodosMutation(); const [createTodo] = usePostTodosMutation();
|
|
140
|
+
await createTodo({body}).unwrap(); createTodo({data}); // sync, returns {mutationId, id}
|
|
141
|
+
const [patchTodo] = usePatchTodosByIdMutation(); const [patchTodo] = usePatchTodosByIdMutation();
|
|
142
|
+
const [deleteTodo] = useDeleteTodosByIdMutation(); const [deleteTodo] = useDeleteTodosByIdMutation();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`SYNC_COLLECTIONS` replaces the hand-maintained list in `example-frontend/store/syncdb.ts`, closing the "client string must match backend route" gap.
|
|
146
|
+
|
|
147
|
+
### CLI
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
terreno-syncdb-codegen --schema <url|path> --out <file> [--collections a,b] [--config <json>] [--no-format]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- `--schema` (required): OpenAPI URL or local JSON file (same source as `openapi-config.ts`'s `schemaFile`).
|
|
154
|
+
- `--out` (required): output `.ts` path.
|
|
155
|
+
- `--collections` (optional): comma-separated allowlist; also the fallback when the spec has no `x-terreno-sync` extensions (older backends). Errors clearly if neither extensions nor the flag identify any collection.
|
|
156
|
+
- `--config` (optional): JSON file with `{overrides: {todos: {retries: false}}}` per-collection settings.
|
|
157
|
+
- `--no-format` (optional): skip the biome/prettier formatting pass (codegen formats output via `Bun.spawn`-ed `bunx biome check --write` when available, mirroring `generate-sdk.ts`; falls back to raw emit).
|
|
158
|
+
|
|
159
|
+
## Models
|
|
160
|
+
|
|
161
|
+
No Mongoose model changes. One local-storage schema change in syncdb: the `_outbox` reserved table gains an optional `maxAttempts` numeric cell (`syncdb/src/storage/schema.ts`, `syncdb/src/mutations/outbox.ts`). Missing cell means engine default, so existing persisted stores need no migration.
|
|
162
|
+
|
|
163
|
+
## APIs
|
|
164
|
+
|
|
165
|
+
No new HTTP endpoints. One OpenAPI spec addition in `@terreno/api`: list operations of sync-enabled model routers gain
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
"x-terreno-sync": {"collection": "todos", "scope": "owner"}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Vendor extensions are legal OpenAPI and ignored by the RTK codegen, Swagger UI, and the AJV validator, so this is backward-compatible.
|
|
172
|
+
|
|
173
|
+
## Notifications
|
|
174
|
+
|
|
175
|
+
None needed.
|
|
176
|
+
|
|
177
|
+
## UI
|
|
178
|
+
|
|
179
|
+
No new screens. `example-frontend/components/SyncTodosScreen.tsx` swaps its hand-rolled `SyncTodo` interface and string-based hooks for the generated `useGetTodosQuery`/`usePostTodosMutation`/`usePatchTodosByIdMutation`/`useDeleteTodosByIdMutation`, and `store/syncdb.ts` imports `SYNC_COLLECTIONS` from the generated file. Behavior is unchanged; the existing syncdb Playwright e2e suite is the regression net.
|
|
180
|
+
|
|
181
|
+
## Phases
|
|
182
|
+
|
|
183
|
+
Single PR is plausible but the work splits cleanly into four phases if review prefers smaller units:
|
|
184
|
+
|
|
185
|
+
1. **syncdb runtime** — `createCollectionHooks` + `maxAttempts` plumbing (mutate → outbox → replay coordinator), with unit tests. No consumer-visible breaking changes.
|
|
186
|
+
2. **api spec extension** — `x-terreno-sync` on list operations, with an OpenAPI generation test asserting the extension appears for `example-backend`-style sync configs and is absent otherwise.
|
|
187
|
+
3. **codegen package** — new `syncdb-codegen/` workspace package: arg parsing, spec loading, collection discovery, type emitter, file emitter, snapshot tests against a fixture spec captured from `example-backend`, `build:binary` script.
|
|
188
|
+
4. **integration + docs** — `example-frontend` `sync-sdk` script and screen migration; README updates (`syncdb/README.md`, new `syncdb-codegen/README.md`, `docs/how-to/migrate-rtk-to-syncdb.md` note); root `package.json` convenience scripts.
|
|
189
|
+
|
|
190
|
+
## Feature Flags & Migrations
|
|
191
|
+
|
|
192
|
+
- No new feature flags. The existing `use-syncdb` flag continues to gate the syncdb path in `example-frontend`.
|
|
193
|
+
- No data migrations. The new `_outbox.maxAttempts` cell is optional and defaulted.
|
|
194
|
+
- Publishing: not wired into `publish-on-tag.yml` in this IP (neither is `@terreno/syncdb` yet); tracked in Future Work so both are added together.
|
|
195
|
+
|
|
196
|
+
## Activity Log & User Updates
|
|
197
|
+
|
|
198
|
+
None — developer tooling only.
|
|
199
|
+
|
|
200
|
+
## Not Included / Future Work
|
|
201
|
+
|
|
202
|
+
- Publishing `@terreno/syncdb` and `@terreno/syncdb-codegen` to npm (add `publish-syncdb` + `publish-syncdb-codegen` jobs to `publish-on-tag.yml`).
|
|
203
|
+
- Per-platform prebuilt binaries as release artifacts.
|
|
204
|
+
- Generated wrappers for `useSyncStatus`/`useConflicts` (no per-collection typing to add).
|
|
205
|
+
- Server-driven filters/sorts in `useQuery` options (syncdb filters run client-side in JS; nothing for codegen to type beyond `TData`).
|
|
206
|
+
- Zod/AJV runtime validation of mutation payloads against the OpenAPI schema.
|
|
207
|
+
- Custom hook naming via config (`overrides.todos.hookPrefix`), for apps that need to avoid the intentional name overlap with RTK-generated hooks.
|
|
208
|
+
- RTK-compatible async mutation state (`isLoading`, promise-returning triggers with `.unwrap()`), should a drop-in compatibility mode ever be wanted.
|
|
209
|
+
- A `--watch` mode.
|
|
210
|
+
|
|
211
|
+
## Files to Create / Modify
|
|
212
|
+
|
|
213
|
+
**Create**
|
|
214
|
+
- `syncdb-codegen/package.json` — name `@terreno/syncdb-codegen`, `bin`, `build:binary`, catalog deps (dev-only: typescript, biome).
|
|
215
|
+
- `syncdb-codegen/tsconfig.json`, `syncdb-codegen/biome.jsonc` — copied from `syncdb/` siblings.
|
|
216
|
+
- `syncdb-codegen/src/cli.ts` — `#!/usr/bin/env bun` entry; arg parsing (`Bun.argv` + `util.parseArgs`), exit codes, error messages.
|
|
217
|
+
- `syncdb-codegen/src/loadSpec.ts` — fetch/read + JSON parse of the OpenAPI document.
|
|
218
|
+
- `syncdb-codegen/src/discoverCollections.ts` — walk `paths`, collect `x-terreno-sync` operations, resolve entity/create/update schema refs from the list/create/patch operations; apply `--collections` filter/fallback.
|
|
219
|
+
- `syncdb-codegen/src/emitTypes.ts` — OpenAPI schema subset → TS interfaces.
|
|
220
|
+
- `syncdb-codegen/src/emitSdk.ts` — assemble the output file (header, types, `SYNC_COLLECTIONS`, `createCollectionHooks` calls with overrides applied).
|
|
221
|
+
- `syncdb-codegen/src/*.test.ts` — unit + snapshot tests against `syncdb-codegen/src/fixtures/openapi.example.json`.
|
|
222
|
+
- `syncdb-codegen/README.md` — usage, args, config format, custom-hooks pattern.
|
|
223
|
+
- `example-frontend/syncdb-codegen.json` — per-collection overrides example (todos with default retries).
|
|
224
|
+
|
|
225
|
+
**Modify**
|
|
226
|
+
- `syncdb/src/react/collectionHooks.ts` (new file in existing package) + `syncdb/src/react/index.ts` — `createCollectionHooks` export.
|
|
227
|
+
- `syncdb/src/client.ts` — `MutateArgs.maxAttempts?`, pass-through to outbox.
|
|
228
|
+
- `syncdb/src/mutations/outbox.ts`, `syncdb/src/storage/schema.ts` — optional `maxAttempts` cell.
|
|
229
|
+
- `syncdb/src/sync/replayCoordinator.ts` — `row.maxAttempts ?? MAX_ERROR_NACK_ATTEMPTS` in the error-nack branch.
|
|
230
|
+
- `api/src/openApi.ts` (`listOpenApiMiddleware`) — emit `x-terreno-sync` when `options.sync` is set.
|
|
231
|
+
- `example-frontend/package.json` — `"sync-sdk": "terreno-syncdb-codegen --schema ... --out ./store/syncDbSdk.ts --config ./syncdb-codegen.json"` script; add `@terreno/syncdb-codegen` as a workspace dev dependency.
|
|
232
|
+
- `example-frontend/store/syncDbSdk.ts` — generated output (checked in, like `openApiSdk.ts`).
|
|
233
|
+
- `example-frontend/store/syncdb.ts` — import `SYNC_COLLECTIONS` from the generated file.
|
|
234
|
+
- `example-frontend/components/SyncTodosScreen.tsx` — use generated hooks; delete the local `SyncTodo` interface.
|
|
235
|
+
- Root `package.json` — workspace entry + `syncdb-codegen:compile` / `syncdb-codegen:test` scripts.
|
|
236
|
+
- `syncdb/README.md`, `docs/how-to/migrate-rtk-to-syncdb.md` — codegen sections.
|
|
237
|
+
|
|
238
|
+
## Task List
|
|
239
|
+
|
|
240
|
+
See [docs/tasks/syncdb-codegen.md](../tasks/syncdb-codegen.md).
|
|
241
|
+
|
|
242
|
+
## Acceptance Criteria
|
|
243
|
+
|
|
244
|
+
- [ ] `createCollectionHooks<Todo>({collection: "todos"})` returns the five per-operation hooks (`useListQuery`, `useReadQuery`, `useCreateMutation`, `useUpdateMutation`, `useDeleteMutation`) whose behavior matches direct `useQuery`/`useEntity`/`useMutate` calls; mutation hooks return `[trigger]` tuples so RTK-style destructuring compiles (unit tests in `syncdb/src/react/`).
|
|
245
|
+
- [ ] `createCollectionHooks({collection: "todos", retries: false})` produces mutations whose outbox rows fail terminally after a single `error` nack; `retries: 3` fails after three attempts; omitted keeps the current 5-attempt backoff (replay coordinator tests).
|
|
246
|
+
- [ ] `GET /openapi.json` from a backend with `sync: {scope: {type: "owner"}}` on `/todos` includes `x-terreno-sync: {collection: "todos", scope: "owner"}` on the list operation; non-synced routes have no extension (api test).
|
|
247
|
+
- [ ] `terreno-syncdb-codegen --schema <fixture> --out out.ts` emits a file that type-checks under the example-frontend tsconfig and contains `SYNC_COLLECTIONS`, typed interfaces, and per synced collection the five RTK-named hooks (`useGetTodosQuery`, `useGetTodosByIdQuery`, `usePostTodosMutation`, `usePatchTodosByIdMutation`, `useDeleteTodosByIdMutation`) — names byte-identical to what `@rtk-query/codegen-openapi` derives for the same routes (snapshot test).
|
|
248
|
+
- [ ] `--collections todos` filters output to that collection; a spec without extensions and no `--collections` flag exits non-zero with an actionable message.
|
|
249
|
+
- [ ] A config file with `{overrides: {todos: {retries: false}}}` produces `createCollectionHooks<...>({collection: "todos", retries: false})` in the output.
|
|
250
|
+
- [ ] `bun run build:binary` in `syncdb-codegen/` produces a standalone executable that generates identical output to the bin entry.
|
|
251
|
+
- [ ] `bun run sync-sdk` in `example-frontend` (backend running) regenerates `store/syncDbSdk.ts` with no diff when the backend is unchanged; SyncTodosScreen works against the generated hooks (existing syncdb e2e suite passes).
|
|
252
|
+
- [ ] `bun run lint`, `bun run syncdb:test`, `bun run api:test`, and the new `syncdb-codegen:test` pass.
|