@terreno/mcp 57.0.0-beta.1 → 57.1.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.
Files changed (222) hide show
  1. package/dist/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +381 -191
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  6. package/dist/docs/resources/overview.md +6 -1
  7. package/dist/docs/resources/rtk.md +3 -1
  8. package/dist/docs/resources/syncdb.md +43 -0
  9. package/dist/docs/upgrades/56.0.0-beta.2.md +34 -0
  10. package/dist/docs/upgrades/57.0.0.md +49 -0
  11. package/dist/docs/upgrades/57.1.0.md +126 -0
  12. package/dist/docs/versioned/next/README.md +15 -3
  13. package/dist/docs/versioned/next/explanation/README.md +3 -0
  14. package/dist/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  15. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  16. package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  17. package/dist/docs/versioned/next/explanation/authentication.md +51 -58
  18. package/dist/docs/versioned/next/explanation/local-first-data.md +52 -0
  19. package/dist/docs/versioned/next/explanation/loop-engineering.md +117 -0
  20. package/dist/docs/versioned/next/explanation/repository-settings.md +8 -0
  21. package/dist/docs/versioned/next/explanation/roadmap-process.md +22 -9
  22. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  23. package/dist/docs/versioned/next/how-to/README.md +3 -0
  24. package/dist/docs/versioned/next/how-to/admin-add-model.md +41 -0
  25. package/dist/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  26. package/dist/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  27. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  28. package/dist/docs/versioned/next/how-to/circleci.md +173 -0
  29. package/dist/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  30. package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  31. package/dist/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  32. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  33. package/dist/docs/versioned/next/how-to/websocket-integration.md +2 -1
  34. package/dist/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  35. package/dist/docs/versioned/next/implementationPlans/README.md +31 -17
  36. package/dist/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  37. package/dist/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  38. package/dist/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  39. package/dist/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  40. package/dist/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  41. package/dist/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  42. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  43. package/dist/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  44. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  45. package/dist/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  46. package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  47. package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  48. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  49. package/dist/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  50. package/dist/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  51. package/dist/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  52. package/dist/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  53. package/dist/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  54. package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  55. package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  56. package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  57. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  58. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  59. package/dist/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  60. package/dist/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  61. package/dist/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  62. package/dist/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  63. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  64. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  65. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  66. package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  67. package/dist/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  68. package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  69. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  70. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  71. package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  72. package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  73. package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  74. package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  75. package/dist/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  76. package/dist/docs/versioned/next/reference/README.md +6 -1
  77. package/dist/docs/versioned/next/reference/admin-config.md +51 -0
  78. package/dist/docs/versioned/next/reference/ai.md +15 -1
  79. package/dist/docs/versioned/next/reference/api.md +18 -0
  80. package/dist/docs/versioned/next/reference/comms.md +88 -7
  81. package/dist/docs/versioned/next/reference/environment-variables.md +9 -1
  82. package/dist/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  83. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  84. package/dist/docs/versioned/next/reference/mcp-server.md +7 -0
  85. package/dist/docs/versioned/next/reference/syncdb.md +30 -3
  86. package/dist/docs/versioned/next/tasks/README.md +21 -10
  87. package/dist/docs/versioned/next/tasks/admin-improvements.md +189 -353
  88. package/dist/docs/versioned/next/tasks/admin-only.md +2 -0
  89. package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  90. package/dist/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  91. package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  92. package/dist/docs/versioned/next/tasks/consent-forms.md +2 -0
  93. package/dist/docs/versioned/next/tasks/feature-flags.md +2 -0
  94. package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  95. package/dist/docs/versioned/next/tasks/model-router-actions.md +2 -0
  96. package/dist/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  97. package/dist/docs/versioned/next/tasks/offline-mode.md +2 -0
  98. package/dist/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  99. package/dist/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  100. package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  101. package/dist/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  102. package/dist/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  103. package/dist/index.js +24 -55
  104. package/dist/index.js.map +1 -1
  105. package/dist/local/localServer.d.ts.map +1 -1
  106. package/dist/local/localServer.js +9 -8
  107. package/dist/local/localServer.js.map +1 -1
  108. package/dist/local/localTools.d.ts +1 -1
  109. package/dist/local/localTools.d.ts.map +1 -1
  110. package/dist/prompts.d.ts.map +1 -1
  111. package/dist/prompts.js +44 -45
  112. package/dist/prompts.js.map +1 -1
  113. package/dist/resources.d.ts.map +1 -1
  114. package/dist/resources.js +9 -2
  115. package/dist/resources.js.map +1 -1
  116. package/dist/tools.d.ts +1 -1
  117. package/dist/tools.d.ts.map +1 -1
  118. package/dist/tools.js +4 -2
  119. package/dist/tools.js.map +1 -1
  120. package/package.json +7 -3
  121. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  122. package/src/docs/resources/overview.md +6 -1
  123. package/src/docs/resources/rtk.md +3 -1
  124. package/src/docs/resources/syncdb.md +43 -0
  125. package/src/docs/upgrades/56.0.0-beta.2.md +34 -0
  126. package/src/docs/upgrades/57.0.0.md +49 -0
  127. package/src/docs/upgrades/57.1.0.md +126 -0
  128. package/src/docs/versioned/next/README.md +15 -3
  129. package/src/docs/versioned/next/explanation/README.md +3 -0
  130. package/src/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  131. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  132. package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  133. package/src/docs/versioned/next/explanation/authentication.md +51 -58
  134. package/src/docs/versioned/next/explanation/local-first-data.md +52 -0
  135. package/src/docs/versioned/next/explanation/loop-engineering.md +117 -0
  136. package/src/docs/versioned/next/explanation/repository-settings.md +8 -0
  137. package/src/docs/versioned/next/explanation/roadmap-process.md +22 -9
  138. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  139. package/src/docs/versioned/next/how-to/README.md +3 -0
  140. package/src/docs/versioned/next/how-to/admin-add-model.md +41 -0
  141. package/src/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  142. package/src/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  143. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  144. package/src/docs/versioned/next/how-to/circleci.md +173 -0
  145. package/src/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  146. package/src/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  147. package/src/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  148. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  149. package/src/docs/versioned/next/how-to/websocket-integration.md +2 -1
  150. package/src/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  151. package/src/docs/versioned/next/implementationPlans/README.md +31 -17
  152. package/src/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  153. package/src/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  154. package/src/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  155. package/src/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  156. package/src/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  157. package/src/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  158. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  159. package/src/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  160. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  161. package/src/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  162. package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  163. package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  164. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  165. package/src/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  166. package/src/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  167. package/src/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  168. package/src/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  169. package/src/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  170. package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  171. package/src/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  172. package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  173. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  174. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  175. package/src/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  176. package/src/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  177. package/src/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  178. package/src/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  179. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  180. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  181. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  182. package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  183. package/src/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  184. package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  185. package/src/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  186. package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  187. package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  188. package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  189. package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  190. package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  191. package/src/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  192. package/src/docs/versioned/next/reference/README.md +6 -1
  193. package/src/docs/versioned/next/reference/admin-config.md +51 -0
  194. package/src/docs/versioned/next/reference/ai.md +15 -1
  195. package/src/docs/versioned/next/reference/api.md +18 -0
  196. package/src/docs/versioned/next/reference/comms.md +88 -7
  197. package/src/docs/versioned/next/reference/environment-variables.md +9 -1
  198. package/src/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  199. package/src/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  200. package/src/docs/versioned/next/reference/mcp-server.md +7 -0
  201. package/src/docs/versioned/next/reference/syncdb.md +30 -3
  202. package/src/docs/versioned/next/tasks/README.md +21 -10
  203. package/src/docs/versioned/next/tasks/admin-improvements.md +189 -353
  204. package/src/docs/versioned/next/tasks/admin-only.md +2 -0
  205. package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  206. package/src/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  207. package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  208. package/src/docs/versioned/next/tasks/consent-forms.md +2 -0
  209. package/src/docs/versioned/next/tasks/feature-flags.md +2 -0
  210. package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  211. package/src/docs/versioned/next/tasks/model-router-actions.md +2 -0
  212. package/src/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  213. package/src/docs/versioned/next/tasks/offline-mode.md +2 -0
  214. package/src/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  215. package/src/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  216. package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  217. package/src/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  218. package/src/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  219. package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  220. package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
  221. package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  222. package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
package/dist/bootstrap.js CHANGED
@@ -71,7 +71,7 @@ A full-stack application built with the Terreno framework.
71
71
 
72
72
  ## Project Structure
73
73
 
74
- - **frontend/** - Expo/React Native frontend using @terreno/ui and @terreno/rtk
74
+ - **frontend/** - Expo/React Native frontend using @terreno/ui, @terreno/syncdb, and @terreno/rtk (SDK + Better Auth)
75
75
  - **backend/** - Express/Mongoose backend using @terreno/api
76
76
 
77
77
  ## Development
@@ -118,7 +118,8 @@ cd frontend && bun run sdk # Regenerate SDK after backend changes
118
118
  - Use \`Model.findExactlyOne\` or \`Model.findOneOrNone\` (not \`Model.findOne\`)
119
119
 
120
120
  ### Frontend Conventions
121
- - Use generated SDK hooks from \`@/store/openApiSdk\`
121
+ - Use \`@terreno/syncdb/react\` hooks (\`useQuery\`, \`useMutate\`) for synced collection CRUD
122
+ - Use generated SDK hooks from \`@/store/openApiSdk\` for non-synced routes only
122
123
  - Use @terreno/ui components (Box, Page, Button, TextField, etc.)
123
124
  - Never modify \`openApiSdk.ts\` manually - regenerate with \`bun run sdk\`
124
125
 
@@ -350,11 +351,22 @@ const generateBackendIndex = () => {
350
351
  const generateBackendServer = (args) => {
351
352
  const { appDisplayName } = args;
352
353
  return `import {AdminApp} from "@terreno/admin-backend";
353
- import {checkModelsStrict, logger, TerrenoApp} from "@terreno/api";
354
+ import {
355
+ BetterAuthApp,
356
+ checkModelsStrict,
357
+ createBetterAuth,
358
+ getMongoClientFromMongoose,
359
+ logger,
360
+ RealtimeApp,
361
+ SyncApp,
362
+ TerrenoApp,
363
+ type UserModel as TerrenoAuthUserModel,
364
+ } from "@terreno/api";
354
365
  import type express from "express";
355
366
  import {userRouter} from "./api/users";
356
367
  import {AppConfiguration} from "./models/appConfiguration";
357
368
  import {User} from "./models/user";
369
+ import {buildBetterAuthConfig, getWebOrigins} from "./utils/betterAuthConfig";
358
370
  import {connectToMongoDB} from "./utils/database";
359
371
 
360
372
  const isDeployed = process.env.NODE_ENV === "production";
@@ -368,18 +380,40 @@ export async function start(skipListen = false): Promise<express.Application> {
368
380
  checkModelsStrict();
369
381
  }
370
382
 
383
+ const betterAuthConfig = buildBetterAuthConfig();
384
+ const betterAuthInstance = betterAuthConfig
385
+ ? createBetterAuth({
386
+ config: betterAuthConfig,
387
+ mongoClient: getMongoClientFromMongoose(),
388
+ // noExplicitAny: User model type mismatch
389
+ // biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
390
+ userModel: User as any,
391
+ })
392
+ : undefined;
393
+
371
394
  const app = new TerrenoApp({
395
+ corsOrigin: getWebOrigins(),
372
396
  loggingOptions: {
373
397
  disableConsoleColors: isDeployed,
374
398
  level: "debug",
375
399
  logRequests: !isDeployed,
376
400
  },
377
401
  skipListen,
378
- // noExplicitAny: Typing User model
379
- // biome-ignore lint/suspicious/noExplicitAny: Typing User model
402
+ // noExplicitAny: User model type mismatch
403
+ // biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
380
404
  userModel: User as any,
381
- })
382
- .configure(AppConfiguration)
405
+ }).configure(AppConfiguration);
406
+
407
+ if (betterAuthConfig) {
408
+ app.register(
409
+ new BetterAuthApp({
410
+ config: betterAuthConfig,
411
+ userModel: User as unknown as TerrenoAuthUserModel,
412
+ })
413
+ );
414
+ }
415
+
416
+ app
383
417
  .register(userRouter)
384
418
  .register(
385
419
  new AdminApp({
@@ -387,7 +421,7 @@ export async function start(skipListen = false): Promise<express.Application> {
387
421
  {
388
422
  displayName: "Users",
389
423
  listFields: ["email", "name", "admin", "created"],
390
- // noExplicitAny: User model type mismatch
424
+ // noExplicitAny: User model type mismatch
391
425
  // biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
392
426
  model: User as any,
393
427
  routePath: "/users",
@@ -395,6 +429,22 @@ export async function start(skipListen = false): Promise<express.Application> {
395
429
  ],
396
430
  })
397
431
  )
432
+ .register(new SyncApp())
433
+ .register(
434
+ new RealtimeApp({
435
+ betterAuth: betterAuthInstance
436
+ ? {
437
+ auth: betterAuthInstance,
438
+ // noExplicitAny: User model type mismatch
439
+ // biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
440
+ userModel: User as any,
441
+ }
442
+ : undefined,
443
+ // noExplicitAny: User model type mismatch
444
+ // biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
445
+ userModel: User as any,
446
+ })
447
+ )
398
448
  .start();
399
449
 
400
450
  return app;
@@ -417,7 +467,8 @@ export const connectToMongoDB = async (): Promise<void> => {
417
467
  return;
418
468
  }
419
469
 
420
- const mongoURI = process.env.MONGO_URI || "mongodb://localhost:27017/${dbName}";
470
+ const mongoURI =
471
+ process.env.MONGO_URI || "mongodb://127.0.0.1:27017/${dbName}?replicaSet=rs0";
421
472
 
422
473
  try {
423
474
  await mongoose.connect(mongoURI);
@@ -437,6 +488,46 @@ export const connectToMongoDB = async (): Promise<void> => {
437
488
  };
438
489
  `;
439
490
  };
491
+ const generateBackendBetterAuthConfig = (args) => {
492
+ const { appName } = args;
493
+ return `import type {AuthProvider, BetterAuthConfig} from "@terreno/api";
494
+
495
+ const DEFAULT_BETTER_AUTH_SECRET = "${appName}-better-auth-secret-dev-only-32chars";
496
+ const DEFAULT_BETTER_AUTH_URL = "http://localhost:4000";
497
+ const DEFAULT_WEB_ORIGINS = ["http://localhost:8082", "http://127.0.0.1:8082"];
498
+ const APP_SCHEMES = ["${appName}://", "exp://"];
499
+
500
+ export const getAuthProvider = (): AuthProvider => {
501
+ const provider = process.env.AUTH_PROVIDER as AuthProvider | undefined;
502
+ return provider ?? "better-auth";
503
+ };
504
+
505
+ export const getWebOrigins = (): string[] => {
506
+ const origins = new Set<string>(DEFAULT_WEB_ORIGINS);
507
+ const fromEnv = process.env.CORS_ORIGINS?.split(",")
508
+ .map((value) => value.trim())
509
+ .filter(Boolean);
510
+ for (const origin of fromEnv ?? []) {
511
+ origins.add(origin);
512
+ }
513
+ return [...origins];
514
+ };
515
+
516
+ export const buildBetterAuthConfig = (): BetterAuthConfig | undefined => {
517
+ if (getAuthProvider() !== "better-auth") {
518
+ return undefined;
519
+ }
520
+
521
+ return {
522
+ baseURL: process.env.BETTER_AUTH_URL ?? DEFAULT_BETTER_AUTH_URL,
523
+ crossDomainCookies: process.env.CROSS_DOMAIN_AUTH_COOKIES === "true",
524
+ enabled: true,
525
+ secret: process.env.BETTER_AUTH_SECRET ?? DEFAULT_BETTER_AUTH_SECRET,
526
+ trustedOrigins: [...APP_SCHEMES, ...getWebOrigins()],
527
+ };
528
+ };
529
+ `;
530
+ };
440
531
  const generateBackendModelPlugins = () => {
441
532
  return `import {
442
533
  createdUpdatedPlugin,
@@ -468,6 +559,11 @@ const userSchema = new mongoose.Schema<UserDocument, UserModel>(
468
559
  default: false,
469
560
  type: Boolean,
470
561
  },
562
+ betterAuthId: {
563
+ index: true,
564
+ sparse: true,
565
+ type: String,
566
+ },
471
567
  email: {
472
568
  lowercase: true,
473
569
  required: true,
@@ -642,6 +738,7 @@ export type UserDocument = DefaultDoc &
642
738
  UserMethods &
643
739
  mongoose.PassportLocalDocument & {
644
740
  admin: boolean;
741
+ betterAuthId?: string;
645
742
  email: string;
646
743
  name: string;
647
744
  };
@@ -655,9 +752,12 @@ const generateFrontendPackageJson = (args) => {
655
752
  "@sentry/react": "^10.29.0",
656
753
  "@terreno/admin-frontend": "latest",
657
754
  "@terreno/rtk": "latest",
755
+ "@terreno/syncdb": "latest",
658
756
  "@terreno/ui": "latest",
659
757
  expo: "~57.0.14",
758
+ "expo-constants": "~18.0.13",
660
759
  "expo-router": "~57.0.14",
760
+ "expo-sqlite": "~16.0.10",
661
761
  react: "19.2.3",
662
762
  "react-dom": "19.2.3",
663
763
  "react-native": "0.86.2",
@@ -927,11 +1027,13 @@ import {Stack} from "expo-router";
927
1027
  import * as SplashScreen from "expo-splash-screen";
928
1028
  import {useEffect} from "react";
929
1029
  import "react-native-reanimated";
930
- import {baseUrl, useSelectCurrentUserId} from "@terreno/rtk";
931
- import {TerrenoProvider} from "@terreno/ui";
932
- import {Provider} from "react-redux";
1030
+ import {baseUrl, selectBetterAuthIsLoading, selectBetterAuthUserId} from "@terreno/rtk";
1031
+ import {SyncDbProvider} from "@terreno/syncdb/react";
1032
+ import {Spinner, TerrenoProvider} from "@terreno/ui";
1033
+ import {Provider, useSelector} from "react-redux";
933
1034
  import {PersistGate} from "redux-persist/integration/react";
934
- import store, {persistor} from "@/store/index";
1035
+ import store, {persistor, syncBetterAuthSession} from "@/store/index";
1036
+ import {setSyncDbReady, syncDb} from "@/store/syncdb";
935
1037
 
936
1038
  export {ErrorBoundary} from "expo-router";
937
1039
 
@@ -977,139 +1079,171 @@ export default function RootLayout(): React.ReactElement | null {
977
1079
  }
978
1080
 
979
1081
  function RootLayoutNav(): React.ReactElement {
980
- const userId = useSelectCurrentUserId();
1082
+ const userId = useSelector(selectBetterAuthUserId) ?? undefined;
1083
+ const isAuthLoading = useSelector(selectBetterAuthIsLoading);
1084
+
1085
+ // Hydrate Better Auth session into Redux on startup.
1086
+ useEffect(() => {
1087
+ void syncBetterAuthSession(store.dispatch);
1088
+ }, []);
1089
+
1090
+ // Start syncdb after login; stop on logout.
1091
+ useEffect(() => {
1092
+ if (!userId) {
1093
+ setSyncDbReady(false);
1094
+ return;
1095
+ }
1096
+ let stopped = false;
1097
+ void syncDb
1098
+ .start()
1099
+ .then(() => {
1100
+ if (!stopped) {
1101
+ setSyncDbReady(true);
1102
+ }
1103
+ })
1104
+ .catch((startError: unknown) => {
1105
+ console.error("[syncdb] start failed", startError);
1106
+ setSyncDbReady(false);
1107
+ });
1108
+ return () => {
1109
+ stopped = true;
1110
+ setSyncDbReady(false);
1111
+ void syncDb.stop();
1112
+ };
1113
+ }, [userId]);
1114
+
1115
+ if (isAuthLoading) {
1116
+ return <Spinner />;
1117
+ }
981
1118
 
982
1119
  return (
983
- <ThemeProvider value={DefaultTheme}>
984
- <Stack>
985
- {!userId ? (
986
- <Stack.Screen name="login" options={{headerShown: false}} />
987
- ) : (
988
- <Stack.Screen name="(tabs)" options={{headerShown: false}} />
989
- )}
990
- </Stack>
991
- </ThemeProvider>
1120
+ <SyncDbProvider client={syncDb}>
1121
+ <ThemeProvider value={DefaultTheme}>
1122
+ <Stack>
1123
+ {!userId ? (
1124
+ <>
1125
+ <Stack.Screen name="login" options={{headerShown: false}} />
1126
+ <Stack.Screen name="signup" options={{headerShown: false}} />
1127
+ </>
1128
+ ) : (
1129
+ <Stack.Screen name="(tabs)" options={{headerShown: false}} />
1130
+ )}
1131
+ </Stack>
1132
+ </ThemeProvider>
1133
+ </SyncDbProvider>
992
1134
  );
993
1135
  }
994
1136
  `;
995
1137
  };
996
1138
  const generateFrontendLogin = () => {
997
- return `import {Box, Button, Heading, Page, Text, TextField, useToast} from "@terreno/ui";
1139
+ return `import {baseUrl} from "@terreno/rtk";
1140
+ import {LoginScreen} from "@terreno/ui";
1141
+ import {useRouter} from "expo-router";
998
1142
  import type React from "react";
999
1143
  import {useCallback, useState} from "react";
1000
- import {useEmailLoginMutation, useEmailSignUpMutation} from "@/store/sdk";
1001
-
1002
- const LoginScreen: React.FC = () => {
1003
- const [name, setName] = useState<string>("");
1004
- const [email, setEmail] = useState<string>("");
1005
- const [password, setPassword] = useState<string>("");
1006
- const [isSignUp, setIsSignUp] = useState<boolean>(false);
1007
- const toast = useToast();
1144
+ import {betterAuthClient} from "@/lib/betterAuth";
1145
+ import {syncBetterAuthSession, useAppDispatch} from "@/store/index";
1008
1146
 
1009
- const [emailLogin, {isLoading: isLoginLoading, error: loginError}] = useEmailLoginMutation();
1010
- const [emailSignUp, {isLoading: isSignUpLoading, error: signUpError}] = useEmailSignUpMutation();
1147
+ const Login: React.FC = () => {
1148
+ const router = useRouter();
1149
+ const dispatch = useAppDispatch();
1150
+ const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
1151
+ const [isSubmitting, setIsSubmitting] = useState(false);
1152
+
1153
+ const handleSubmit = useCallback(
1154
+ async (values: Record<string, string>): Promise<void> => {
1155
+ const {email, password} = values;
1156
+ setErrorMessage(undefined);
1157
+ setIsSubmitting(true);
1158
+ try {
1159
+ const result = await betterAuthClient.signIn.email({email, password});
1160
+ if (result.error) {
1161
+ setErrorMessage(result.error.message ?? "Sign in failed.");
1162
+ return;
1163
+ }
1164
+ await syncBetterAuthSession(dispatch);
1165
+ router.replace("/(tabs)");
1166
+ } catch (error: unknown) {
1167
+ console.error("[login] Sign in threw", {baseUrl, error});
1168
+ setErrorMessage("Sign in failed. Please try again.");
1169
+ } finally {
1170
+ setIsSubmitting(false);
1171
+ }
1172
+ },
1173
+ [dispatch, router]
1174
+ );
1011
1175
 
1012
- const handleSubmit = useCallback(async (): Promise<void> => {
1013
- if (!email || !password) {
1014
- toast.warn("Email and password are required");
1015
- return;
1016
- }
1176
+ return (
1177
+ <LoginScreen
1178
+ error={errorMessage}
1179
+ fields={[
1180
+ {autoComplete: "off", label: "Email", name: "email", required: true, type: "email"},
1181
+ {label: "Password", name: "password", required: true, type: "password"},
1182
+ ]}
1183
+ loading={isSubmitting}
1184
+ onSignUpPress={() => router.push("/signup")}
1185
+ onSubmit={handleSubmit}
1186
+ title="Welcome"
1187
+ />
1188
+ );
1189
+ };
1017
1190
 
1018
- if (isSignUp && !name) {
1019
- toast.warn("Signup requires name");
1020
- return;
1021
- }
1191
+ export default Login;
1192
+ `;
1193
+ };
1194
+ const generateFrontendSignup = () => {
1195
+ return `import {SignUpScreen, simplePasswordRequirements} from "@terreno/ui";
1196
+ import {useRouter} from "expo-router";
1197
+ import type React from "react";
1198
+ import {useCallback, useState} from "react";
1199
+ import {betterAuthClient} from "@/lib/betterAuth";
1200
+ import {syncBetterAuthSession, useAppDispatch} from "@/store/index";
1022
1201
 
1023
- try {
1024
- if (isSignUp) {
1025
- await emailSignUp({email, name, password}).unwrap();
1026
- } else {
1027
- await emailLogin({email, password}).unwrap();
1202
+ const SignUp: React.FC = () => {
1203
+ const router = useRouter();
1204
+ const dispatch = useAppDispatch();
1205
+ const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
1206
+ const [isSubmitting, setIsSubmitting] = useState(false);
1207
+
1208
+ const handleSubmit = useCallback(
1209
+ async (values: Record<string, string>): Promise<void> => {
1210
+ const {email, password, name} = values;
1211
+ setErrorMessage(undefined);
1212
+ setIsSubmitting(true);
1213
+ try {
1214
+ const result = await betterAuthClient.signUp.email({email, name, password});
1215
+ if (result.error) {
1216
+ setErrorMessage(result.error.message ?? "Sign up failed.");
1217
+ return;
1218
+ }
1219
+ await syncBetterAuthSession(dispatch);
1220
+ router.replace("/(tabs)");
1221
+ } catch {
1222
+ setErrorMessage("Sign up failed. Please try again.");
1223
+ } finally {
1224
+ setIsSubmitting(false);
1028
1225
  }
1029
- } catch (err) {
1030
- console.error("Authentication error:", err);
1031
- }
1032
- }, [email, password, name, isSignUp, emailLogin, emailSignUp, toast]);
1033
-
1034
- const toggleMode = useCallback((): void => {
1035
- setIsSignUp(!isSignUp);
1036
- }, [isSignUp]);
1037
-
1038
- const isLoading = isLoginLoading || isSignUpLoading;
1039
- const error = loginError || signUpError;
1040
- const isSubmitDisabled = !email || !password || (isSignUp && !name) || isLoading;
1226
+ },
1227
+ [dispatch, router]
1228
+ );
1041
1229
 
1042
1230
  return (
1043
- <Page navigation={undefined}>
1044
- <Box
1045
- alignItems="center"
1046
- alignSelf="center"
1047
- flex="grow"
1048
- justifyContent="center"
1049
- maxWidth={400}
1050
- padding={4}
1051
- width="100%"
1052
- >
1053
- <Box marginBottom={8}>
1054
- <Heading>{isSignUp ? "Create Account" : "Welcome Back"}</Heading>
1055
- </Box>
1056
- <Box gap={4} width="100%">
1057
- {isSignUp && (
1058
- <TextField
1059
- disabled={isLoading}
1060
- onChange={setName}
1061
- placeholder="Name"
1062
- title="Name"
1063
- value={name}
1064
- />
1065
- )}
1066
- <TextField
1067
- autoComplete="off"
1068
- disabled={isLoading}
1069
- onChange={setEmail}
1070
- placeholder="Email"
1071
- title="Email"
1072
- type="email"
1073
- value={email}
1074
- />
1075
- <TextField
1076
- disabled={isLoading}
1077
- onChange={setPassword}
1078
- placeholder="Password"
1079
- title="Password"
1080
- type="password"
1081
- value={password}
1082
- />
1083
- {Boolean(error) && (
1084
- <Text color="error">
1085
- {(error as {data?: {message?: string}})?.data?.message || "An error occurred"}
1086
- </Text>
1087
- )}
1088
- <Box marginTop={4}>
1089
- <Button
1090
- disabled={isSubmitDisabled}
1091
- fullWidth
1092
- loading={isLoading}
1093
- onClick={handleSubmit}
1094
- text={isSignUp ? "Sign Up" : "Login"}
1095
- />
1096
- </Box>
1097
- <Box marginTop={2}>
1098
- <Button
1099
- disabled={isLoading}
1100
- fullWidth
1101
- onClick={toggleMode}
1102
- text={isSignUp ? "Already have an account? Login" : "Need an account? Sign Up"}
1103
- variant="outline"
1104
- />
1105
- </Box>
1106
- </Box>
1107
- </Box>
1108
- </Page>
1231
+ <SignUpScreen
1232
+ error={errorMessage}
1233
+ fields={[
1234
+ {label: "Name", name: "name", required: true, type: "text"},
1235
+ {autoComplete: "off", label: "Email", name: "email", required: true, type: "email"},
1236
+ {label: "Password", name: "password", required: true, type: "password"},
1237
+ ]}
1238
+ loading={isSubmitting}
1239
+ onLoginPress={() => router.back()}
1240
+ onSubmit={handleSubmit}
1241
+ passwordRequirements={simplePasswordRequirements}
1242
+ />
1109
1243
  );
1110
1244
  };
1111
1245
 
1112
- export default LoginScreen;
1246
+ export default SignUp;
1113
1247
  `;
1114
1248
  };
1115
1249
  const generateFrontendTabsLayout = () => {
@@ -1188,16 +1322,18 @@ const generateFrontendTabsProfile = () => {
1188
1322
  return `import {Box, Button, Heading, Page, Text} from "@terreno/ui";
1189
1323
  import type React from "react";
1190
1324
  import {useCallback} from "react";
1325
+ import {signOut} from "@/lib/betterAuth";
1326
+ import {logout, syncBetterAuthSession, useAppDispatch} from "@/store/index";
1191
1327
  import {useGetMeQuery} from "@/store/sdk";
1192
- import {logout} from "@/store/index";
1193
- import {useAppDispatch} from "@/store/index";
1194
1328
 
1195
1329
  const ProfileScreen: React.FC = () => {
1196
1330
  const dispatch = useAppDispatch();
1197
1331
  const {data: profile, isLoading} = useGetMeQuery();
1198
1332
 
1199
- const handleLogout = useCallback((): void => {
1333
+ const handleLogout = useCallback(async (): Promise<void> => {
1334
+ await signOut();
1200
1335
  dispatch(logout());
1336
+ await syncBetterAuthSession(dispatch);
1201
1337
  }, [dispatch]);
1202
1338
 
1203
1339
  if (isLoading) {
@@ -1301,19 +1437,21 @@ export default NotFoundScreen;
1301
1437
  const generateFrontendStoreIndex = () => {
1302
1438
  return `import AsyncStorage from "@react-native-async-storage/async-storage";
1303
1439
  import {combineReducers, configureStore} from "@reduxjs/toolkit";
1304
- import {generateAuthSlice, registerTerrenoDevStore} from "@terreno/rtk";
1440
+ import {generateBetterAuthSlice, registerTerrenoDevStore} from "@terreno/rtk";
1305
1441
  import {DateTime} from "luxon";
1306
1442
  import {useDispatch} from "react-redux";
1307
1443
  import type {Storage} from "redux-persist";
1308
1444
  import {persistReducer, persistStore} from "redux-persist";
1445
+ import {betterAuthClient} from "@/lib/betterAuth";
1309
1446
 
1310
1447
  import appState from "./appState";
1311
1448
  import {rtkQueryErrorMiddleware} from "./errors";
1312
1449
  import {terrenoApi} from "./sdk";
1313
1450
 
1314
- const authSlice = generateAuthSlice(terrenoApi);
1451
+ const betterAuth = generateBetterAuthSlice({authClient: betterAuthClient});
1315
1452
 
1316
- export const {logout} = authSlice;
1453
+ export const logout = betterAuth.actions.logout;
1454
+ export const syncBetterAuthSession = betterAuth.syncSession;
1317
1455
 
1318
1456
  const createSafeStorage = (): Storage => {
1319
1457
  return {
@@ -1337,7 +1475,7 @@ const createSafeStorage = (): Storage => {
1337
1475
  };
1338
1476
 
1339
1477
  const persistConfig = {
1340
- blacklist: ["terreno-rtk"],
1478
+ blacklist: ["terreno-rtk", "betterAuth"],
1341
1479
  key: "root",
1342
1480
  storage: createSafeStorage(),
1343
1481
  timeout: 0,
@@ -1346,7 +1484,7 @@ const persistConfig = {
1346
1484
 
1347
1485
  const rootReducer = combineReducers({
1348
1486
  appState,
1349
- auth: authSlice.authReducer,
1487
+ betterAuth: betterAuth.reducer,
1350
1488
  "terreno-rtk": terrenoApi.reducer,
1351
1489
  });
1352
1490
 
@@ -1367,7 +1505,7 @@ const store = configureStore({
1367
1505
  serializableCheck: false,
1368
1506
  thunk: true,
1369
1507
  }).concat([
1370
- ...authSlice.middleware,
1508
+ ...betterAuth.middleware,
1371
1509
  // noExplicitAny: RTK Query middleware typing
1372
1510
  // biome-ignore lint/suspicious/noExplicitAny: RTK Query middleware typing
1373
1511
  terrenoApi.middleware as any,
@@ -1543,8 +1681,6 @@ export const terrenoApi = openapi
1543
1681
  });
1544
1682
 
1545
1683
  export const {
1546
- useEmailLoginMutation,
1547
- useEmailSignUpMutation,
1548
1684
  useGetMeQuery,
1549
1685
  usePatchMeMutation,
1550
1686
  } = terrenoApi;
@@ -1586,45 +1722,10 @@ const injectedRtkApi = api
1586
1722
  addTagTypes,
1587
1723
  })
1588
1724
  .injectEndpoints({
1589
- endpoints: (build) => ({
1590
- emailLogin: build.mutation<EmailLoginRes, EmailLoginArgs>({
1591
- query: (queryArg) => ({
1592
- body: queryArg,
1593
- method: "POST",
1594
- url: \`/auth/login\`,
1595
- }),
1596
- }),
1597
- emailSignUp: build.mutation<EmailSignUpRes, EmailSignUpArgs>({
1598
- query: (queryArg) => ({
1599
- body: queryArg,
1600
- method: "POST",
1601
- url: \`/auth/signup\`,
1602
- }),
1603
- }),
1604
- }),
1725
+ endpoints: () => ({}),
1605
1726
  overrideExisting: false,
1606
1727
  });
1607
1728
  export {injectedRtkApi as openapi};
1608
- export type EmailLoginArgs = {
1609
- email: string;
1610
- password: string;
1611
- };
1612
- export type EmailLoginRes = {
1613
- token: string;
1614
- refreshToken: string;
1615
- userId: string;
1616
- };
1617
- export type EmailSignUpArgs = {
1618
- email: string;
1619
- password: string;
1620
- name: string;
1621
- };
1622
- export type EmailSignUpRes = {
1623
- token: string;
1624
- refreshToken: string;
1625
- userId: string;
1626
- };
1627
- export const {useEmailLoginMutation, useEmailSignUpMutation} = injectedRtkApi;
1628
1729
  `;
1629
1730
  };
1630
1731
  const generateFrontendTheme = () => {
@@ -1749,6 +1850,85 @@ const generateFrontendEnv = () => {
1749
1850
  return `EXPO_PUBLIC_API_URL=http://localhost:4000
1750
1851
  `;
1751
1852
  };
1853
+ const generateBackendEnv = (args) => {
1854
+ const { appName } = args;
1855
+ const dbName = appName.replace(/-/g, "_");
1856
+ return `MONGO_URI=mongodb://127.0.0.1:27017/${dbName}?replicaSet=rs0
1857
+ AUTH_PROVIDER=better-auth
1858
+ BETTER_AUTH_SECRET=${appName}-better-auth-secret-dev-only-32chars
1859
+ BETTER_AUTH_URL=http://localhost:4000
1860
+ TOKEN_SECRET=dev-token-secret
1861
+ TOKEN_ISSUER=${appName}-dev
1862
+ REFRESH_TOKEN_SECRET=dev-refresh-secret
1863
+ SESSION_SECRET=dev-session-secret
1864
+ PORT=4000
1865
+ `;
1866
+ };
1867
+ const generateFrontendBetterAuthLib = (args) => {
1868
+ const { appName } = args;
1869
+ return `import {baseUrl, createBetterAuthClient} from "@terreno/rtk";
1870
+ import Constants from "expo-constants";
1871
+
1872
+ const getAppScheme = (): string => {
1873
+ const expoExtra = Constants.expoConfig?.extra;
1874
+ return expoExtra?.scheme ?? "${appName}";
1875
+ };
1876
+
1877
+ export const betterAuthClient = createBetterAuthClient({
1878
+ baseURL: baseUrl,
1879
+ scheme: getAppScheme(),
1880
+ storagePrefix: "${appName}",
1881
+ });
1882
+
1883
+ export const signOut = async (): Promise<void> => {
1884
+ await betterAuthClient.signOut();
1885
+ };
1886
+ `;
1887
+ };
1888
+ const generateFrontendSyncDb = (args) => {
1889
+ const { appName } = args;
1890
+ const dbName = appName.replace(/-/g, "-");
1891
+ return `import {baseUrl} from "@terreno/rtk";
1892
+ import {betterAuthAdapter, createSyncDb, type SyncDb} from "@terreno/syncdb";
1893
+ import {betterAuthClient} from "@/lib/betterAuth";
1894
+
1895
+ export const SYNC_DB_NAME = "${dbName}";
1896
+
1897
+ /** Add synced collection names here as you register modelRouter sync scopes on the backend. */
1898
+ export const SYNC_COLLECTIONS: string[] = [];
1899
+
1900
+ const authProvider = betterAuthAdapter(betterAuthClient, {pollIntervalMs: 60_000});
1901
+
1902
+ export const syncDb: SyncDb = createSyncDb({
1903
+ authProvider,
1904
+ baseUrl,
1905
+ collections: SYNC_COLLECTIONS,
1906
+ name: SYNC_DB_NAME,
1907
+ });
1908
+
1909
+ let syncDbReady = false;
1910
+ const syncDbReadyListeners = new Set<() => void>();
1911
+
1912
+ export const setSyncDbReady = (ready: boolean): void => {
1913
+ if (syncDbReady === ready) {
1914
+ return;
1915
+ }
1916
+ syncDbReady = ready;
1917
+ for (const listener of syncDbReadyListeners) {
1918
+ listener();
1919
+ }
1920
+ };
1921
+
1922
+ export const subscribeSyncDbReady = (listener: () => void): (() => void) => {
1923
+ syncDbReadyListeners.add(listener);
1924
+ return () => {
1925
+ syncDbReadyListeners.delete(listener);
1926
+ };
1927
+ };
1928
+
1929
+ export const getSyncDbReadySnapshot = (): boolean => syncDbReady;
1930
+ `;
1931
+ };
1752
1932
  const generateFrontendUtilsIndex = () => {
1753
1933
  return `export const captureException = (error: unknown): void => {
1754
1934
  console.error("Captured exception:", error);
@@ -1860,7 +2040,7 @@ ${appDescription}
1860
2040
 
1861
2041
  ## Project Structure
1862
2042
 
1863
- - **frontend/** - Expo/React Native frontend using @terreno/ui and @terreno/rtk
2043
+ - **frontend/** - Expo/React Native frontend using @terreno/ui, @terreno/syncdb, and @terreno/rtk (SDK + Better Auth)
1864
2044
  - **backend/** - Express/Mongoose backend using @terreno/api
1865
2045
 
1866
2046
  ## Development
@@ -2048,7 +2228,7 @@ globs: ["**/*"]
2048
2228
 
2049
2229
  # ${appDisplayName} Frontend
2050
2230
 
2051
- Expo/React Native frontend using @terreno/ui and @terreno/rtk.
2231
+ Expo/React Native frontend using @terreno/ui, @terreno/syncdb, and @terreno/rtk.
2052
2232
 
2053
2233
  ## Development
2054
2234
 
@@ -2060,11 +2240,12 @@ bun run lint # Lint code
2060
2240
 
2061
2241
  ## Frontend Conventions
2062
2242
 
2063
- - Use generated SDK hooks from \`@/store/openApiSdk\`
2243
+ - Use \`@terreno/syncdb/react\` hooks (\`useQuery\`, \`useMutate\`) for synced collection CRUD
2244
+ - Use generated SDK hooks from \`@/store/openApiSdk\` for non-synced routes only
2064
2245
  - Use @terreno/ui components (Box, Page, Button, TextField, etc.)
2065
2246
  - Never modify \`openApiSdk.ts\` manually - regenerate with \`bun run sdk\`
2066
2247
  - Use Luxon for date operations
2067
- - Use Redux Toolkit for state management
2248
+ - Better Auth session: \`generateBetterAuthSlice\` + \`lib/betterAuth.ts\`
2068
2249
  ${admin}## Expo SDK upgrades
2069
2250
 
2070
2251
  - For Expo/React Native dependency upgrades, install and use the official \`upgrading-expo\` skill from \`expo/skills\` (\`bunx skills add expo/skills\` when using the Skills CLI). It covers \`expo install\`, doctor, caches, and SDK breaking changes.
@@ -2082,7 +2263,7 @@ ${admin}## Expo SDK upgrades
2082
2263
  1. Regenerate SDK if backend changed: \`bun run sdk\`
2083
2264
  2. Create screen in \`app/\` directory
2084
2265
  3. Use @terreno/ui components for layout
2085
- 4. Use SDK hooks for data fetching
2266
+ 4. Use SDK hooks for non-synced routes; use syncdb hooks for synced collections
2086
2267
  `;
2087
2268
  };
2088
2269
  const _generateFrontendClaudeRulesFile = (args) => {
@@ -2096,7 +2277,7 @@ globs: ["**/*"]
2096
2277
 
2097
2278
  # ${appDisplayName} Frontend
2098
2279
 
2099
- Expo/React Native frontend using @terreno/ui and @terreno/rtk.
2280
+ Expo/React Native frontend using @terreno/ui, @terreno/syncdb, and @terreno/rtk.
2100
2281
 
2101
2282
  ## Development
2102
2283
 
@@ -2108,18 +2289,19 @@ bun run lint # Lint code
2108
2289
 
2109
2290
  ## Frontend Conventions
2110
2291
 
2111
- - Use generated SDK hooks from \`@/store/openApiSdk\`
2292
+ - Use \`@terreno/syncdb/react\` hooks for synced collection CRUD
2293
+ - Use generated SDK hooks from \`@/store/openApiSdk\` for non-synced routes only
2112
2294
  - Use @terreno/ui components (Box, Page, Button, TextField, etc.)
2113
2295
  - Never modify \`openApiSdk.ts\` manually - regenerate with \`bun run sdk\`
2114
2296
  - Use Luxon for date operations
2115
- - Use Redux Toolkit for state management
2297
+ - Better Auth session: \`generateBetterAuthSlice\` + \`lib/betterAuth.ts\`
2116
2298
 
2117
2299
  ## Adding a New Screen
2118
2300
 
2119
2301
  1. Regenerate SDK if backend changed: \`bun run sdk\`
2120
2302
  2. Create screen in \`app/\` directory
2121
2303
  3. Use @terreno/ui components for layout
2122
- 4. Use SDK hooks for data fetching
2304
+ 4. Use SDK hooks for non-synced routes; use syncdb hooks for synced collections
2123
2305
  `;
2124
2306
  };
2125
2307
  const generateAiRulesFiles = (args) => {
@@ -2286,6 +2468,11 @@ const generateAllFiles = (args) => {
2286
2468
  { content: generateBackendIndex(), path: `${backendDir}/src/index.ts` },
2287
2469
  { content: generateBackendServer(args), path: `${backendDir}/src/server.ts` },
2288
2470
  { content: generateBackendDatabase(args), path: `${backendDir}/src/utils/database.ts` },
2471
+ {
2472
+ content: generateBackendBetterAuthConfig(args),
2473
+ path: `${backendDir}/src/utils/betterAuthConfig.ts`,
2474
+ },
2475
+ { content: generateBackendEnv(args), path: `${backendDir}/.env` },
2289
2476
  { content: generateBackendModelPlugins(), path: `${backendDir}/src/models/modelPlugins.ts` },
2290
2477
  { content: generateBackendUserModel(), path: `${backendDir}/src/models/user.ts` },
2291
2478
  {
@@ -2307,6 +2494,7 @@ const generateAllFiles = (args) => {
2307
2494
  { content: generateFrontendGenerateSdk(), path: `${frontendDir}/scripts/generate-sdk.ts` },
2308
2495
  { content: generateFrontendRootLayout(args), path: `${frontendDir}/app/_layout.tsx` },
2309
2496
  { content: generateFrontendLogin(), path: `${frontendDir}/app/login.tsx` },
2497
+ { content: generateFrontendSignup(), path: `${frontendDir}/app/signup.tsx` },
2310
2498
  { content: generateFrontendNotFound(), path: `${frontendDir}/app/+not-found.tsx` },
2311
2499
  { content: generateFrontendTabsLayout(), path: `${frontendDir}/app/(tabs)/_layout.tsx` },
2312
2500
  { content: generateFrontendTabsIndex(args), path: `${frontendDir}/app/(tabs)/index.tsx` },
@@ -2322,6 +2510,8 @@ const generateAllFiles = (args) => {
2322
2510
  { content: generateFrontendStoreErrors(), path: `${frontendDir}/store/errors.ts` },
2323
2511
  { content: generateFrontendStoreSdk(), path: `${frontendDir}/store/sdk.ts` },
2324
2512
  { content: generateFrontendStoreOpenApiSdk(), path: `${frontendDir}/store/openApiSdk.ts` },
2513
+ { content: generateFrontendBetterAuthLib(args), path: `${frontendDir}/lib/betterAuth.ts` },
2514
+ { content: generateFrontendSyncDb(args), path: `${frontendDir}/store/syncdb.ts` },
2325
2515
  { content: generateFrontendTheme(), path: `${frontendDir}/constants/theme.ts` },
2326
2516
  { content: generateFrontendUtilsIndex(), path: `${frontendDir}/utils/index.ts` },
2327
2517
  { content: generateFrontendEnv(), path: `${frontendDir}/.env` },
@@ -2396,12 +2586,11 @@ ${fileList}
2396
2586
  cd ../frontend && bun install
2397
2587
  \`\`\`
2398
2588
 
2399
- 5. **Start MongoDB** (required for backend):
2589
+ 5. **Start MongoDB as a replica set** (required for realtime/sync):
2400
2590
  \`\`\`bash
2401
- # Using Docker:
2402
- docker run -d -p 27017:27017 mongo:latest
2403
-
2404
- # Or install MongoDB locally
2591
+ # Using Docker (single-node replica set):
2592
+ docker run -d --name mongo -p 27017:27017 mongo:7 --replSet rs0
2593
+ docker exec mongo mongosh --eval 'rs.initiate({_id:"rs0",members:[{_id:0,host:"127.0.0.1:27017"}]})'
2405
2594
  \`\`\`
2406
2595
 
2407
2596
  6. **Start the backend:**
@@ -2498,10 +2687,11 @@ After generating the files:
2498
2687
  7. Start the frontend with \`bun run web\`
2499
2688
 
2500
2689
  The application should include:
2501
- - Full authentication flow (login/signup)
2690
+ - Better Auth login (email/password via \`@terreno/ui\` LoginScreen)
2502
2691
  - Tab-based navigation with Home and Profile screens
2503
- - Redux state management with persistence
2504
- - RTK Query SDK generation from OpenAPI spec
2692
+ - \`@terreno/syncdb\` client wired in the root layout (empty \`SYNC_COLLECTIONS\` until you add synced models)
2693
+ - Redux with Better Auth session + RTK Query SDK for non-synced routes
2694
+ - OpenAPI SDK generation from backend spec
2505
2695
  - Cursor rules for AI assistance
2506
2696
  - MCP integration for development assistance
2507
2697