@terreno/mcp 57.21.0 → 58.0.0-beta.1

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.
Files changed (244) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +57 -3
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +17 -16
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
  7. package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
  8. package/dist/docs/prompts/debug.md +6 -4
  9. package/dist/docs/prompts/screen.md +11 -17
  10. package/dist/docs/resources/overview.md +5 -3
  11. package/dist/docs/resources/patterns.md +157 -238
  12. package/dist/docs/resources/syncdb.md +44 -16
  13. package/dist/docs/templates/bootstrap/CLAUDE.template.md +2 -2
  14. package/dist/docs/templates/bootstrap/cursorrules.template.md +7 -5
  15. package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
  16. package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
  17. package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
  18. package/dist/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
  19. package/dist/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
  20. package/dist/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
  21. package/dist/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
  22. package/dist/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
  23. package/dist/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
  24. package/dist/docs/upgrades/58.0.0-beta.1.md +504 -0
  25. package/dist/docs/upgrades/58.0.0.md +495 -4
  26. package/dist/docs/versioned/next/README.md +2 -2
  27. package/dist/docs/versioned/next/approval-rubric.md +2 -2
  28. package/dist/docs/versioned/next/explanation/admin-interface.md +6 -5
  29. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
  30. package/dist/docs/versioned/next/explanation/ai-workflows.md +5 -3
  31. package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
  32. package/dist/docs/versioned/next/explanation/authentication.md +62 -43
  33. package/dist/docs/versioned/next/explanation/dependency-management.md +1 -1
  34. package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  35. package/dist/docs/versioned/next/explanation/example-coverage.md +2 -2
  36. package/dist/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
  37. package/dist/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
  38. package/dist/docs/versioned/next/explanation/local-first-data.md +11 -9
  39. package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
  40. package/dist/docs/versioned/next/explanation/modular-api-design.md +2 -2
  41. package/dist/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
  42. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
  43. package/dist/docs/versioned/next/explanation/source-rules.md +2 -2
  44. package/dist/docs/versioned/next/explanation/typescript-configuration.md +2 -4
  45. package/dist/docs/versioned/next/explanation/versioning-policy.md +3 -3
  46. package/dist/docs/versioned/next/how-to/README.md +3 -2
  47. package/dist/docs/versioned/next/how-to/add-feature-flags.md +50 -25
  48. package/dist/docs/versioned/next/how-to/add-organizations.md +2 -1
  49. package/dist/docs/versioned/next/how-to/admin-custom-home.md +8 -2
  50. package/dist/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
  51. package/dist/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
  52. package/dist/docs/versioned/next/how-to/build-admin-screens.md +10 -8
  53. package/dist/docs/versioned/next/how-to/build-for-web.md +1 -1
  54. package/dist/docs/versioned/next/how-to/call-external-apis.md +2 -2
  55. package/dist/docs/versioned/next/how-to/circleci.md +12 -6
  56. package/dist/docs/versioned/next/how-to/configure-better-auth.md +114 -60
  57. package/dist/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
  58. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
  59. package/dist/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
  60. package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
  61. package/dist/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
  62. package/dist/docs/versioned/next/how-to/github-actions-ci.md +2 -2
  63. package/dist/docs/versioned/next/how-to/in-app-notifications.md +1 -1
  64. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
  65. package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
  66. package/dist/docs/versioned/next/how-to/product-announcements.md +6 -3
  67. package/dist/docs/versioned/next/how-to/rate-limiting.md +38 -16
  68. package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
  69. package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -3
  70. package/dist/docs/versioned/next/how-to/set-up-social-login.md +1 -1
  71. package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
  72. package/dist/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
  73. package/dist/docs/versioned/next/how-to/upgrade-banner.md +5 -3
  74. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
  75. package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
  76. package/dist/docs/versioned/next/how-to/websocket-integration.md +113 -402
  77. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  78. package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
  79. package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
  80. package/dist/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
  81. package/dist/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
  82. package/dist/docs/versioned/next/reference/README.md +1 -1
  83. package/dist/docs/versioned/next/reference/admin-frontend.md +85 -70
  84. package/dist/docs/versioned/next/reference/admin-spa.md +10 -4
  85. package/dist/docs/versioned/next/reference/ai.md +36 -4
  86. package/dist/docs/versioned/next/reference/announcements.md +3 -3
  87. package/dist/docs/versioned/next/reference/api.md +39 -24
  88. package/dist/docs/versioned/next/reference/cli.md +3 -4
  89. package/dist/docs/versioned/next/reference/environment-variables.md +16 -16
  90. package/dist/docs/versioned/next/reference/feature-flags.md +48 -7
  91. package/dist/docs/versioned/next/reference/jobs.md +2 -2
  92. package/dist/docs/versioned/next/reference/legacy/rtk.md +25 -600
  93. package/dist/docs/versioned/next/reference/mcp-server.md +441 -1158
  94. package/dist/docs/versioned/next/reference/syncdb.md +426 -16
  95. package/dist/docs/versioned/next/reference/test.md +28 -3
  96. package/dist/docs/versioned/next/reference/ui.md +132 -19
  97. package/dist/docs/versioned/next/tasks/native-module-baseline.md +8 -4
  98. package/dist/docs/versioned/next/tasks/rate-limiting.md +9 -2
  99. package/dist/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
  100. package/dist/docs/versioned/next/tasks/remove-rtk.md +155 -0
  101. package/dist/local/localTools.d.ts.map +1 -1
  102. package/dist/local/localTools.js +3 -54
  103. package/dist/local/localTools.js.map +1 -1
  104. package/dist/local/tools/runtime.d.ts +0 -21
  105. package/dist/local/tools/runtime.d.ts.map +1 -1
  106. package/dist/local/tools/runtime.js +0 -176
  107. package/dist/local/tools/runtime.js.map +1 -1
  108. package/dist/local/tools/syncDb.d.ts +0 -11
  109. package/dist/local/tools/syncDb.d.ts.map +1 -1
  110. package/dist/local/tools/syncDb.js +1 -5
  111. package/dist/local/tools/syncDb.js.map +1 -1
  112. package/dist/packageGuidelines.d.ts +3 -2
  113. package/dist/packageGuidelines.d.ts.map +1 -1
  114. package/dist/packageGuidelines.js +4 -11
  115. package/dist/packageGuidelines.js.map +1 -1
  116. package/dist/prompts.d.ts.map +1 -1
  117. package/dist/prompts.js +17 -18
  118. package/dist/prompts.js.map +1 -1
  119. package/dist/resources.d.ts.map +1 -1
  120. package/dist/resources.js +1 -8
  121. package/dist/resources.js.map +1 -1
  122. package/dist/search/chunker.d.ts +1 -1
  123. package/dist/search/chunker.d.ts.map +1 -1
  124. package/dist/search/inferPackages.d.ts +1 -1
  125. package/dist/search/inferPackages.js +5 -5
  126. package/dist/search/inferPackages.js.map +1 -1
  127. package/dist/tools.d.ts +6 -0
  128. package/dist/tools.d.ts.map +1 -1
  129. package/dist/tools.js +126 -119
  130. package/dist/tools.js.map +1 -1
  131. package/package.json +6 -6
  132. package/src/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
  133. package/src/docs/guidelines/ui/guidelines/core.md +1 -1
  134. package/src/docs/prompts/debug.md +6 -4
  135. package/src/docs/prompts/screen.md +11 -17
  136. package/src/docs/resources/overview.md +5 -3
  137. package/src/docs/resources/patterns.md +157 -238
  138. package/src/docs/resources/syncdb.md +44 -16
  139. package/src/docs/templates/bootstrap/CLAUDE.template.md +2 -2
  140. package/src/docs/templates/bootstrap/cursorrules.template.md +7 -5
  141. package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
  142. package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
  143. package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
  144. package/src/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
  145. package/src/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
  146. package/src/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
  147. package/src/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
  148. package/src/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
  149. package/src/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
  150. package/src/docs/upgrades/58.0.0-beta.1.md +504 -0
  151. package/src/docs/upgrades/58.0.0.md +495 -4
  152. package/src/docs/versioned/next/README.md +2 -2
  153. package/src/docs/versioned/next/approval-rubric.md +2 -2
  154. package/src/docs/versioned/next/explanation/admin-interface.md +6 -5
  155. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
  156. package/src/docs/versioned/next/explanation/ai-workflows.md +5 -3
  157. package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
  158. package/src/docs/versioned/next/explanation/authentication.md +62 -43
  159. package/src/docs/versioned/next/explanation/dependency-management.md +1 -1
  160. package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  161. package/src/docs/versioned/next/explanation/example-coverage.md +2 -2
  162. package/src/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
  163. package/src/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
  164. package/src/docs/versioned/next/explanation/local-first-data.md +11 -9
  165. package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
  166. package/src/docs/versioned/next/explanation/modular-api-design.md +2 -2
  167. package/src/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
  168. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
  169. package/src/docs/versioned/next/explanation/source-rules.md +2 -2
  170. package/src/docs/versioned/next/explanation/typescript-configuration.md +2 -4
  171. package/src/docs/versioned/next/explanation/versioning-policy.md +3 -3
  172. package/src/docs/versioned/next/how-to/README.md +3 -2
  173. package/src/docs/versioned/next/how-to/add-feature-flags.md +50 -25
  174. package/src/docs/versioned/next/how-to/add-organizations.md +2 -1
  175. package/src/docs/versioned/next/how-to/admin-custom-home.md +8 -2
  176. package/src/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
  177. package/src/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
  178. package/src/docs/versioned/next/how-to/build-admin-screens.md +10 -8
  179. package/src/docs/versioned/next/how-to/build-for-web.md +1 -1
  180. package/src/docs/versioned/next/how-to/call-external-apis.md +2 -2
  181. package/src/docs/versioned/next/how-to/circleci.md +12 -6
  182. package/src/docs/versioned/next/how-to/configure-better-auth.md +114 -60
  183. package/src/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
  184. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
  185. package/src/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
  186. package/src/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
  187. package/src/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
  188. package/src/docs/versioned/next/how-to/github-actions-ci.md +2 -2
  189. package/src/docs/versioned/next/how-to/in-app-notifications.md +1 -1
  190. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
  191. package/src/docs/versioned/next/how-to/password-reset.md +1 -1
  192. package/src/docs/versioned/next/how-to/product-announcements.md +6 -3
  193. package/src/docs/versioned/next/how-to/rate-limiting.md +38 -16
  194. package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
  195. package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -3
  196. package/src/docs/versioned/next/how-to/set-up-social-login.md +1 -1
  197. package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
  198. package/src/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
  199. package/src/docs/versioned/next/how-to/upgrade-banner.md +5 -3
  200. package/src/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
  201. package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
  202. package/src/docs/versioned/next/how-to/websocket-integration.md +113 -402
  203. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  204. package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
  205. package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
  206. package/src/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
  207. package/src/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
  208. package/src/docs/versioned/next/reference/README.md +1 -1
  209. package/src/docs/versioned/next/reference/admin-frontend.md +85 -70
  210. package/src/docs/versioned/next/reference/admin-spa.md +10 -4
  211. package/src/docs/versioned/next/reference/ai.md +36 -4
  212. package/src/docs/versioned/next/reference/announcements.md +3 -3
  213. package/src/docs/versioned/next/reference/api.md +39 -24
  214. package/src/docs/versioned/next/reference/cli.md +3 -4
  215. package/src/docs/versioned/next/reference/environment-variables.md +16 -16
  216. package/src/docs/versioned/next/reference/feature-flags.md +48 -7
  217. package/src/docs/versioned/next/reference/jobs.md +2 -2
  218. package/src/docs/versioned/next/reference/legacy/rtk.md +25 -600
  219. package/src/docs/versioned/next/reference/mcp-server.md +441 -1158
  220. package/src/docs/versioned/next/reference/syncdb.md +426 -16
  221. package/src/docs/versioned/next/reference/test.md +28 -3
  222. package/src/docs/versioned/next/reference/ui.md +132 -19
  223. package/src/docs/versioned/next/tasks/native-module-baseline.md +8 -4
  224. package/src/docs/versioned/next/tasks/rate-limiting.md +9 -2
  225. package/src/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
  226. package/src/docs/versioned/next/tasks/remove-rtk.md +155 -0
  227. package/dist/docs/guidelines/rtk/guidelines/core.md +0 -26
  228. package/dist/docs/resources/rtk.md +0 -181
  229. package/dist/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
  230. package/dist/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
  231. package/dist/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
  232. package/dist/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
  233. package/dist/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
  234. package/dist/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
  235. package/dist/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
  236. package/src/docs/guidelines/rtk/guidelines/core.md +0 -26
  237. package/src/docs/resources/rtk.md +0 -181
  238. package/src/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
  239. package/src/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
  240. package/src/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
  241. package/src/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
  242. package/src/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
  243. package/src/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
  244. package/src/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
@@ -1,269 +1,196 @@
1
1
  # Terreno Patterns & Best Practices
2
2
 
3
- Collection CRUD is local-first: the backend syncs a collection with `syncPlugin` and a `sync`
4
- config on `modelRouter`, and the frontend reads and writes it with `@terreno/syncdb/react`
5
- (`useQuery`, `useMutate`). Generated RTK Query hooks from `openApiSdk.ts` are only for routes
6
- that are not synced: auth (`/auth/me`), AI, feature flags, and custom request/response actions.
7
-
8
3
  ## Backend Patterns
9
4
 
10
5
  ### Model Definition
11
6
 
12
- A synced model uses a String `_id` (offline clients mint ids), `isDeletedPlugin` (via the app's
13
- default plugins), and `syncPlugin`. Types live in `src/types/models/`. Every field has a
14
- `description`.
15
-
16
7
  ```typescript
17
- import {syncPlugin} from "@terreno/api";
18
8
  import mongoose from "mongoose";
19
- import type {TodoDocument, TodoModel} from "../types/models/todoTypes";
20
- import {addDefaultPlugins} from "./modelPlugins";
9
+ import { addDefaultPlugins } from "@terreno/api";
10
+
11
+ interface TodoDocument extends mongoose.Document {
12
+ title: string;
13
+ completed: boolean;
14
+ ownerId: mongoose.Types.ObjectId;
15
+ created: Date;
16
+ updated: Date;
17
+ }
18
+
19
+ interface TodoModel extends mongoose.Model<TodoDocument> {
20
+ findByOwner(ownerId: string): Promise<TodoDocument[]>;
21
+ }
21
22
 
22
23
  const todoSchema = new mongoose.Schema<TodoDocument, TodoModel>(
23
24
  {
24
- _id: {
25
- default: (): string => new mongoose.Types.ObjectId().toHexString(),
26
- description: "The document id (String so offline sync clients can mint ids)",
27
- type: String,
28
- },
29
- completed: {
30
- default: false,
31
- description: "Whether the todo item has been completed",
32
- type: Boolean,
33
- },
34
- ownerId: {
35
- description: "The user who owns this todo",
36
- ref: "User",
37
- required: true,
38
- type: mongoose.Schema.Types.ObjectId,
39
- },
40
- title: {
41
- description: "The title of the todo item",
42
- required: true,
43
- trim: true,
44
- type: String,
45
- },
25
+ title: { type: String, required: true, trim: true },
26
+ completed: { type: Boolean, default: false },
27
+ ownerId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
46
28
  },
47
- {strict: "throw", toJSON: {virtuals: true}, toObject: {virtuals: true}}
29
+ {
30
+ strict: "throw",
31
+ toJSON: { virtuals: true },
32
+ toObject: { virtuals: true },
33
+ }
48
34
  );
49
35
 
50
- // addDefaultPlugins applies createdUpdatedPlugin, isDeletedPlugin, findExactlyOne, findOneOrNone.
51
36
  addDefaultPlugins(todoSchema);
52
- // Stamps a per-stream _syncSeq on every write; required by the router's sync config.
53
- todoSchema.plugin(syncPlugin);
37
+
38
+ todoSchema.statics.findByOwner = async function (ownerId: string) {
39
+ return this.find({ ownerId });
40
+ };
54
41
 
55
42
  export const Todo = mongoose.model<TodoDocument, TodoModel>("Todo", todoSchema);
56
43
  ```
57
44
 
58
45
  ### Route Setup
59
46
 
60
- Use the three-argument `modelRouter` and register it on `TerrenoApp` together with `SyncApp`
61
- and `RealtimeApp`. Sync needs a MongoDB replica set (change streams).
62
-
63
47
  ```typescript
64
- import {modelRouter, OwnerQueryFilter, Permissions} from "@terreno/api";
65
- import {Todo} from "../models/todo";
66
- import type {UserDocument} from "../types/models/userTypes";
67
-
68
- export const todoRouter = modelRouter("/todos", Todo, {
69
- permissions: {
70
- create: [Permissions.IsAuthenticated],
71
- delete: [Permissions.IsOwner],
72
- list: [Permissions.IsAuthenticated],
73
- read: [Permissions.IsOwner],
74
- update: [Permissions.IsOwner],
75
- },
76
- preCreate: (body, req) => ({
77
- ...body,
78
- ownerId: (req.user as unknown as UserDocument)?._id,
79
- }),
80
- queryFields: ["completed", "ownerId"],
81
- queryFilter: OwnerQueryFilter,
82
- sort: "-created",
83
- sync: {scope: {type: "owner"}},
84
- });
48
+ import { Router } from "express";
49
+ import { modelRouter, Permissions, OwnerQueryFilter } from "@terreno/api";
50
+ import { Todo, TodoDocument } from "../models/todo";
51
+ import { UserDocument } from "../models/user";
52
+
53
+ export const addTodoRoutes = (router: Router) => {
54
+ router.use(
55
+ "/todos",
56
+ modelRouter(Todo, {
57
+ permissions: {
58
+ create: [Permissions.IsAuthenticated],
59
+ list: [Permissions.IsAuthenticated],
60
+ read: [Permissions.IsOwner],
61
+ update: [Permissions.IsOwner],
62
+ delete: [Permissions.IsOwner],
63
+ },
64
+ queryFields: ["completed"],
65
+ queryFilter: OwnerQueryFilter,
66
+ sort: "-created",
67
+ preCreate: (body, req) => ({
68
+ ...body,
69
+ ownerId: (req.user as UserDocument)?._id,
70
+ }),
71
+ postCreate: async (todo, req) => {
72
+ // Send notification, update stats, etc.
73
+ logger.info("Todo created", { todoId: todo._id });
74
+ },
75
+ })
76
+ );
77
+ };
85
78
  ```
86
79
 
87
80
  ### Custom Endpoints
88
81
 
89
- Add non-CRUD endpoints as `collectionActions` or `instanceActions` on the same `modelRouter`,
90
- not with `router.post`. They get permissions, validation, and OpenAPI automatically.
91
-
92
82
  ```typescript
93
- import {APIError, modelRouter, Permissions, z} from "@terreno/api";
94
-
95
- export const todoRouter = modelRouter("/todos", Todo, {
96
- // ...permissions, sync, and the rest from Route Setup
97
- collectionActions: {
98
- bulkComplete: {
99
- body: z.object({ids: z.array(z.string()).min(1)}).strict(),
100
- handler: async ({body, user}) => {
101
- const result = await Todo.updateMany(
102
- {_id: {$in: body.ids}, ownerId: user?._id},
103
- {$set: {completed: true}}
104
- );
105
- return {matched: result.matchedCount, modified: result.modifiedCount};
106
- },
107
- method: "POST",
108
- permissions: [Permissions.IsAuthenticated],
109
- },
110
- },
111
- instanceActions: {
112
- markComplete: {
113
- handler: async ({doc}) => {
114
- if (doc.completed) {
115
- throw new APIError({status: 409, title: "Todo is already completed"});
116
- }
117
- doc.completed = true;
118
- await doc.save();
119
- return doc;
120
- },
121
- method: "POST",
122
- permissions: [Permissions.IsOwner],
123
- },
124
- },
125
- });
83
+ import { createOpenApiBuilder, APIError, authenticateMiddleware } from "@terreno/api";
84
+
85
+ const toggleCompleteBuilder = createOpenApiBuilder()
86
+ .withTags(["Todos"])
87
+ .withSummary("Toggle todo completion status")
88
+ .withResponse(200, TodoSchema);
89
+
90
+ router.post(
91
+ "/todos/:id/toggle",
92
+ authenticateMiddleware,
93
+ toggleCompleteBuilder.build(),
94
+ async (req, res, next) => {
95
+ try {
96
+ const todo = await Todo.findOneOrThrow({ _id: req.params.id });
97
+
98
+ if (!todo.ownerId.equals((req.user as UserDocument)._id)) {
99
+ throw new APIError({
100
+ status: 403,
101
+ title: "Forbidden",
102
+ detail: "You do not own this todo",
103
+ });
104
+ }
105
+
106
+ todo.completed = !todo.completed;
107
+ await todo.save();
108
+
109
+ res.json(todo);
110
+ } catch (error) {
111
+ next(error);
112
+ }
113
+ }
114
+ );
126
115
  ```
127
116
 
128
117
  ## Frontend Patterns
129
118
 
130
119
  ### Screen with Data Fetching
131
120
 
132
- Synced collections are read from the local store with `useQuery` and written with `useMutate`.
133
- Writes apply immediately and sync through the durable outbox: there is no loading state per
134
- request, no `.unwrap()`, and no refetch after a mutation. Wrap the screen (or the app) in
135
- `SyncDbProvider`, and do not call `mutate` until `syncDb.start()` has resolved.
136
-
137
121
  ```typescript
138
- import {generateMutationId} from "@terreno/syncdb";
139
- import {SyncDbProvider, useMutate, useQuery} from "@terreno/syncdb/react";
140
- import {Box, Button, Card, CheckBox, Heading, IconButton, Text, TextField} from "@terreno/ui";
141
- import {DateTime} from "luxon";
142
- import type React from "react";
143
- import {useCallback, useState} from "react";
144
- import {useSyncDbReady} from "@/hooks/useSyncDbReady";
145
- import {syncDb} from "@/store/syncdb";
146
-
147
- interface Todo {
148
- _id: string;
149
- completed?: boolean;
150
- created: string;
151
- ownerId: string;
152
- title: string;
153
- }
154
-
155
- const TodoList: React.FC = () => {
156
- const isSyncDbReady = useSyncDbReady();
157
- const [newTitle, setNewTitle] = useState<string>("");
158
- const todos = useQuery<Todo>("todos", {sort: "-created"});
159
- const {create, update, remove} = useMutate("todos");
160
-
122
+ import React, { useCallback, useState } from "react";
123
+ import { Box, Page, Text, Button, TextField, ScrollView } from "@terreno/ui";
124
+ import { useMutate, useQuery } from "@terreno/syncdb/react";
125
+
126
+ // `todos` is a synced collection (modelRouter `sync` config + `SYNC_COLLECTIONS`), so reads and
127
+ // writes are local-first through @terreno/syncdb. Non-synced routes use the generated hooks in
128
+ // `@/store/apiSdk` instead (see the form example below).
129
+ const TodoScreen: React.FC = () => {
130
+ const [newTitle, setNewTitle] = useState("");
131
+ // Local reads never wait on the network; useSyncStatus() exposes online/queued/conflict state.
132
+ const todos = useQuery<Todo>("todos", { sort: "-created" });
133
+ const { create } = useMutate("todos");
134
+
135
+ // Writes apply locally right away and queue in the durable outbox until the server confirms.
161
136
  const handleCreate = useCallback((): void => {
162
- const title = newTitle.trim();
163
- if (!title || !isSyncDbReady) {
137
+ if (!newTitle.trim()) {
164
138
  return;
165
139
  }
166
- // Mint the id locally so the optimistic row is addressable before the server acks it.
167
- const id = generateMutationId();
168
- create({data: {_id: id, completed: false, created: DateTime.now().toISO(), title}, id});
140
+ create({ data: { title: newTitle } });
169
141
  setNewTitle("");
170
- }, [create, isSyncDbReady, newTitle]);
171
-
172
- const handleToggle = useCallback(
173
- (todo: Todo): void => {
174
- update({data: {completed: !todo.completed}, id: todo._id});
175
- },
176
- [update]
177
- );
178
-
179
- const handleDelete = useCallback(
180
- (id: string): void => {
181
- remove({id});
182
- },
183
- [remove]
184
- );
142
+ }, [newTitle, create]);
185
143
 
186
144
  return (
187
- <Box gap={3} padding={4} testID="todos-screen">
188
- <Heading size="lg">Todos</Heading>
189
- <Card>
190
- <Box gap={3}>
191
- <TextField
192
- onChange={setNewTitle}
193
- onEnter={handleCreate}
194
- placeholder="New todo"
195
- testID="todos-title-input"
196
- title="New Todo"
197
- value={newTitle}
198
- />
199
- <Button
200
- disabled={!isSyncDbReady || !newTitle.trim()}
201
- iconName="plus"
202
- onClick={handleCreate}
203
- testID="todos-create-button"
204
- text="Add Todo"
205
- />
206
- </Box>
207
- </Card>
208
- {todos.length === 0 ? <Text testID="todos-empty-state">No todos yet</Text> : null}
209
- {todos.map((todo) => (
210
- <Card key={todo._id} testID={`todo-item-${todo._id}`}>
211
- <Box alignItems="center" direction="row" gap={2}>
212
- <Box onClick={() => handleToggle(todo)} testID={`todo-toggle-${todo._id}`}>
213
- <CheckBox selected={Boolean(todo.completed)} size="md" />
145
+ <Page navigation={undefined} title="Todos">
146
+ <ScrollView>
147
+ <Box padding={4} gap={3}>
148
+ <Box direction="row" gap={2}>
149
+ <Box flex={1}>
150
+ <TextField
151
+ placeholder="New todo..."
152
+ value={newTitle}
153
+ onChangeText={setNewTitle}
154
+ />
214
155
  </Box>
215
- <Box flex="grow">
216
- <Text>{todo.title}</Text>
217
- </Box>
218
- <IconButton
219
- accessibilityLabel="Delete todo"
220
- iconName="trash"
221
- onClick={() => handleDelete(todo._id)}
222
- testID={`todo-delete-${todo._id}`}
223
- variant="destructive"
156
+ <Button
157
+ text="Add"
158
+ onClick={handleCreate}
159
+ disabled={!newTitle.trim()}
224
160
  />
225
161
  </Box>
226
- </Card>
227
- ))}
228
- </Box>
162
+
163
+ {todos.map((todo) => (
164
+ <TodoItem key={todo.id} todo={todo} />
165
+ ))}
166
+ </Box>
167
+ </ScrollView>
168
+ </Page>
229
169
  );
230
170
  };
231
171
 
232
- const TodosScreen: React.FC = () => (
233
- <SyncDbProvider client={syncDb}>
234
- <TodoList />
235
- </SyncDbProvider>
236
- );
237
-
238
- export default TodosScreen;
172
+ export default TodoScreen;
239
173
  ```
240
174
 
241
- For large lists, render `useEntityIds` in the list and `useEntity` in each row, so a change to
242
- one row re-renders only that row. Running `terreno-syncdb-codegen` generates typed hooks such as
243
- `useTodo`, `useCreateTodo`, and `useUpdateTodo` in `store/syncDbSdk.ts`, which wrap the same
244
- calls.
245
-
246
175
  ### Form with Validation
247
176
 
248
- A form that calls a non-synced route uses the generated RTK Query mutation and `.unwrap()`.
249
- Here it updates the signed-in user's profile through `PATCH /auth/me`.
250
-
251
177
  ```typescript
252
- import {Box, Button, EmailField, TextField} from "@terreno/ui";
253
- import type React from "react";
254
- import {useCallback, useState} from "react";
255
- import {usePatchMeMutation} from "@/store/sdk";
178
+ import React, { useCallback, useState } from "react";
179
+ import { Box, Button, TextField, EmailField, Text } from "@terreno/ui";
180
+ // Generated by `bun run api-sdk` (terreno-syncdb-codegen --api-out store/apiSdk.ts) from
181
+ // the backend OpenAPI spec. Never edit apiSdk.ts by hand.
182
+ import { usePostUsersMutation } from "@/store/apiSdk";
256
183
 
257
184
  interface FormErrors {
258
185
  name?: string;
259
186
  email?: string;
260
187
  }
261
188
 
262
- const ProfileForm: React.FC = () => {
263
- const [name, setName] = useState<string>("");
264
- const [email, setEmail] = useState<string>("");
189
+ const CreateUserForm: React.FC = () => {
190
+ const [name, setName] = useState("");
191
+ const [email, setEmail] = useState("");
265
192
  const [errors, setErrors] = useState<FormErrors>({});
266
- const [updateProfile, {isLoading}] = usePatchMeMutation();
193
+ const [createUser, { isLoading }] = usePostUsersMutation();
267
194
 
268
195
  const validate = useCallback((): boolean => {
269
196
  const newErrors: FormErrors = {};
@@ -282,43 +209,40 @@ const ProfileForm: React.FC = () => {
282
209
  return Object.keys(newErrors).length === 0;
283
210
  }, [name, email]);
284
211
 
285
- const handleSubmit = useCallback(async (): Promise<void> => {
212
+ const handleSubmit = useCallback(async () => {
286
213
  if (!validate()) {
287
214
  return;
288
215
  }
289
216
 
290
217
  try {
291
- await updateProfile({email, name}).unwrap();
292
- } catch (error: unknown) {
293
- const fields = (error as {data?: {fields?: FormErrors}}).data?.fields;
294
- if (fields) {
295
- setErrors(fields);
218
+ await createUser({ name, email }).unwrap();
219
+ // Success - navigate or show message
220
+ } catch (err: any) {
221
+ if (err.data?.fields) {
222
+ setErrors(err.data.fields);
296
223
  }
297
224
  }
298
- }, [name, email, validate, updateProfile]);
225
+ }, [name, email, validate, createUser]);
299
226
 
300
227
  return (
301
- <Box gap={3} padding={4}>
228
+ <Box padding={4} gap={3}>
302
229
  <TextField
303
- errorText={errors.name}
304
- onChange={setName}
305
- testID="profile-name-input"
306
- title="Name"
230
+ label="Name"
307
231
  value={name}
232
+ onChangeText={setName}
233
+ error={errors.name}
308
234
  />
309
235
  <EmailField
310
- errorText={errors.email}
311
- onChange={setEmail}
312
- testID="profile-email-input"
313
- title="Email"
236
+ label="Email"
314
237
  value={email}
238
+ onChangeText={setEmail}
239
+ error={errors.email}
315
240
  />
316
241
  <Button
317
- fullWidth
318
- loading={isLoading}
242
+ text="Create User"
319
243
  onClick={handleSubmit}
320
- testID="profile-save-button"
321
- text="Save"
244
+ loading={isLoading}
245
+ fullWidth
322
246
  />
323
247
  </Box>
324
248
  );
@@ -361,24 +285,19 @@ throw new APIError({
361
285
 
362
286
  ### Frontend
363
287
 
364
- A synced write does not return the server's answer to the caller. A rejected or conflicting
365
- mutation shows up in `useSyncStatus()` and `useConflicts()`; render the conflict UI from those
366
- hooks. Errors from
367
- non-synced RTK Query routes arrive on `.unwrap()`:
368
-
369
288
  ```typescript
370
289
  try {
371
290
  await someApiCall().unwrap();
372
- } catch (error: unknown) {
373
- const data = (error as {data?: {detail?: string; fields?: Record<string, string>}}).data;
291
+ } catch (err: any) {
374
292
  // Handle field-specific errors
375
- if (data?.fields) {
376
- setErrors(data.fields);
293
+ if (err.data?.fields) {
294
+ setErrors(err.data.fields);
377
295
  return;
378
296
  }
379
297
 
380
298
  // Handle general errors
381
- toast.error(data?.detail ?? "An error occurred");
299
+ const message = err.data?.detail || err.message || "An error occurred";
300
+ toast.error(message);
382
301
  }
383
302
  ```
384
303
 
@@ -1,30 +1,54 @@
1
1
  # @terreno/syncdb Documentation
2
2
 
3
- Local-first data layer for Terreno frontends. The on-device TinyBase store is the UI source of truth.
3
+ Local-first data layer and request client for Terreno frontends. The on-device TinyBase store is
4
+ the UI source of truth for synced collections; every other route goes through the request client
5
+ and generated hooks. Since Terreno 58 there is no Redux and no `@terreno/rtk`.
4
6
 
5
7
  ## Key exports
6
8
 
7
- - `@terreno/syncdb`: `createSyncDb`, `betterAuthAdapter`, `wipeLocalData`, `listConflicts`
8
- - `@terreno/syncdb/react`: `SyncDbProvider`, `useQuery`, `useEntity`, `useMutate`, `useSyncStatus`, `useConflicts`
9
+ - `@terreno/syncdb`: `createSyncDb`, `betterAuthAdapter`, `bridgeBetterAuthReactClient`, `wipeLocalData`, `listConflicts`
10
+ - `@terreno/syncdb/react`: `SyncDbProvider`, `useQuery`, `useEntity`, `useMutate`, `useSyncStatus`, `useConflicts`, `ApiClientProvider`, `useApiQuery`, `useLazyApiQuery`, `useApiMutation`, `useSocketEvent`
11
+ - `@terreno/syncdb/api`: `createApiClient` (auth, refresh-on-401, envelope unwrap, cache + tags)
12
+ - `@terreno/syncdb/auth`: `createBetterAuthClient`, `SessionProvider`, `useSession`
13
+ - `@terreno/syncdb/config`: `baseUrl`, `baseWebsocketsUrl`, `appHeaders`
14
+ - `@terreno/syncdb/flags`: `useFeatureFlags`, `useTerrenoFeatureFlags`
15
+ - `@terreno/syncdb/app`: `useUpgradeCheck`, `canOpenAdminPage`, `installDevConsoleLogger`, `useMCPTools`
9
16
  - `@terreno/syncdb/testing`: `createFakeTransport`
17
+ - `terreno-syncdb-codegen` (bin): `--out store/syncDbSdk.ts` for collections, `--api-out store/apiSdk.ts` for typed request hooks
10
18
 
11
19
  ## Quick start
12
20
 
13
21
  ```typescript
14
- import {baseUrl} from "@terreno/rtk";
15
- import {betterAuthAdapter, createSyncDb} from "@terreno/syncdb";
16
- import {SyncDbProvider, useQuery, useMutate} from "@terreno/syncdb/react";
22
+ import {betterAuthAdapter, bridgeBetterAuthReactClient, createSyncDb} from "@terreno/syncdb";
23
+ import {createApiClient} from "@terreno/syncdb/api";
24
+ import {createBetterAuthClient, useSession} from "@terreno/syncdb/auth";
25
+ import {appHeaders, baseUrl} from "@terreno/syncdb/config";
26
+ import {useMutate, useQuery} from "@terreno/syncdb/react";
27
+ import {useGetAuthMeQuery} from "@/store/apiSdk";
17
28
 
18
- export const syncDb = createSyncDb({
19
- authProvider: betterAuthAdapter(authClient),
29
+ export const betterAuthClient = createBetterAuthClient({baseURL: baseUrl, scheme: "my-app"});
30
+ export const authProvider = betterAuthAdapter(bridgeBetterAuthReactClient(betterAuthClient));
31
+
32
+ export const syncDb = createSyncDb({authProvider, baseUrl, collections: ["todos"], name: "my-app"});
33
+ export const apiClient = createApiClient({
34
+ authProvider,
20
35
  baseUrl,
21
- collections: ["todos"],
22
- name: "my-app",
36
+ credentials: "include",
37
+ headers: appHeaders,
23
38
  });
24
39
 
40
+ // Root layout:
41
+ // <SessionProvider authClient={betterAuthClient} syncDbs={[syncDb]}>
42
+ // <ApiClientProvider client={apiClient}>
43
+ // <SyncDbProvider client={syncDb}>...</SyncDbProvider>
44
+ // </ApiClientProvider>
45
+ // </SessionProvider>
46
+
25
47
  // In a screen:
48
+ const {userId, logout} = useSession();
26
49
  const todos = useQuery<Todo>("todos");
27
50
  const {create, update, remove} = useMutate("todos");
51
+ const {data: me} = useGetAuthMeQuery(); // generated by terreno-syncdb-codegen --api-out
28
52
  ```
29
53
 
30
54
  ## Backend requirements
@@ -34,12 +58,16 @@ const {create, update, remove} = useMutate("todos");
34
58
  - `SyncApp` + `RealtimeApp` on the Express app
35
59
  - MongoDB replica set
36
60
 
37
- ## What syncdb replaces
61
+ ## Synced vs. request routes
38
62
 
39
- Do **not** use RTK Query `useGetXQuery` / `usePostXMutation` for synced
40
- collections. Keep `bun run sdk` for non-synced routes such as auth/me and AI.
41
- Built-in admin String-`_id` CRUD uses windowed syncdb; admin RPC uses its
42
- host-bound fetch client; ObjectId compatibility CRUD remains RTK in Terreno 57.
63
+ - Synced collections: `useQuery` / `useEntity` / `useMutate` (local-first). Never wrap them in
64
+ request hooks.
65
+ - Every other route (`/auth/me`, modelRouter actions, AI, consents, announcements): generated hooks
66
+ in `store/apiSdk.ts` (`useGetAuthMeQuery`, `usePatchAuthMeMutation`, `useLazy…Query`). Regenerate
67
+ with `terreno-syncdb-codegen --schema http://localhost:4000/openapi.json --api-out store/apiSdk.ts`
68
+ (the app's `api-sdk` script) after backend changes. Never edit `apiSdk.ts` by hand.
69
+ - Session: `SessionProvider` / `useSession()` from `@terreno/syncdb/auth`.
70
+ - Admin: windowed syncdb for String-`_id` models; `AdminProvider`'s fetch client for the rest.
43
71
 
44
72
  ## Local MCP debugging
45
73
 
@@ -52,4 +80,4 @@ offline mode, clear events, and merge snapshots when the local MCP starts with
52
80
 
53
81
  Full reference: [docs/reference/syncdb.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/reference/syncdb.md)
54
82
 
55
- Migration: [docs/how-to/migrate-rtk-to-syncdb.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/how-to/migrate-rtk-to-syncdb.md)
83
+ Upgrading from `@terreno/rtk`: [docs/how-to/migrate-rtk-to-syncdb.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/how-to/migrate-rtk-to-syncdb.md)
@@ -20,7 +20,7 @@ cd {{appName}}-backend && bun run dev
20
20
  # Start frontend (port 8082)
21
21
  cd {{appName}}-frontend && bun run web
22
22
 
23
- # Regenerate SDK after backend changes
23
+ # Regenerate store/apiSdk.ts after backend changes (backend running)
24
24
  cd {{appName}}-frontend && bun run sdk
25
25
  ```
26
26
 
@@ -29,7 +29,7 @@ cd {{appName}}-frontend && bun run sdk
29
29
  1. Create model in `{{appName}}-backend/src/models/`
30
30
  2. Create route in `{{appName}}-backend/src/api/`
31
31
  3. Register route in `{{appName}}-backend/src/server.ts`
32
- 4. Regenerate SDK: `cd {{appName}}-frontend && bun run sdk`
32
+ 4. Regenerate API hooks: `cd {{appName}}-frontend && bun run sdk` (writes `store/apiSdk.ts`)
33
33
  5. Create screens in `{{appName}}-frontend/app/`
34
34
 
35
35
  ## Code Style
@@ -4,7 +4,7 @@ A full-stack application built with the Terreno framework.
4
4
 
5
5
  ## Project Structure
6
6
 
7
- - **{{appName}}-frontend/** - Expo/React Native frontend using @terreno/ui and @terreno/rtk
7
+ - **{{appName}}-frontend/** - Expo/React Native frontend using @terreno/ui and @terreno/syncdb
8
8
  - **{{appName}}-backend/** - Express/Mongoose backend using @terreno/api
9
9
 
10
10
  ## Development
@@ -17,7 +17,7 @@ cd {{appName}}-backend && bun run dev # Start backend on port 4000
17
17
 
18
18
  # Frontend
19
19
  cd {{appName}}-frontend && bun run web # Start web frontend
20
- cd {{appName}}-frontend && bun run sdk # Regenerate SDK after backend changes
20
+ cd {{appName}}-frontend && bun run sdk # Regenerate store/apiSdk.ts after backend changes
21
21
  ```
22
22
 
23
23
  ## Code Style
@@ -42,7 +42,7 @@ cd {{appName}}-frontend && bun run sdk # Regenerate SDK after backend changes
42
42
  - Use functional components with `React.FC` type
43
43
  - Add explanatory comment above each `useEffect`
44
44
  - Wrap callbacks in `useCallback`
45
- - Use Redux Toolkit for state management
45
+ - Use React context, local state, or `useStoredState` for app state (no Redux)
46
46
 
47
47
  ### Backend Conventions
48
48
  - Use `modelRouter` for CRUD endpoints
@@ -51,9 +51,11 @@ cd {{appName}}-frontend && bun run sdk # Regenerate SDK after backend changes
51
51
  - Use `Model.findExactlyOne` or `Model.findOneOrNone` (not `Model.findOne`)
52
52
 
53
53
  ### Frontend Conventions
54
- - Use generated SDK hooks from `@/store/openApiSdk`
54
+ - Use `@terreno/syncdb/react` hooks (`useQuery`, `useMutate`) for synced collection CRUD
55
+ - Use generated hooks from `@/store/apiSdk` for non-synced routes only
56
+ - Read the session with `useSession()` from `@terreno/syncdb/auth`
55
57
  - Use @terreno/ui components (Box, Page, Button, TextField, etc.)
56
- - Never modify `openApiSdk.ts` manually - regenerate with `bun run sdk`
58
+ - Never modify `apiSdk.ts` manually - regenerate with `bun run sdk`
57
59
 
58
60
  ## AI Assistance
59
61