@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.
- package/dist/bootstrap.d.ts +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +381 -191
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
- package/dist/docs/resources/overview.md +6 -1
- package/dist/docs/resources/rtk.md +3 -1
- package/dist/docs/resources/syncdb.md +43 -0
- package/dist/docs/upgrades/56.0.0-beta.2.md +34 -0
- package/dist/docs/upgrades/57.0.0.md +49 -0
- package/dist/docs/upgrades/57.1.0.md +126 -0
- package/dist/docs/versioned/next/README.md +15 -3
- package/dist/docs/versioned/next/explanation/README.md +3 -0
- package/dist/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
- package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
- package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
- package/dist/docs/versioned/next/explanation/authentication.md +51 -58
- package/dist/docs/versioned/next/explanation/local-first-data.md +52 -0
- package/dist/docs/versioned/next/explanation/loop-engineering.md +117 -0
- package/dist/docs/versioned/next/explanation/repository-settings.md +8 -0
- package/dist/docs/versioned/next/explanation/roadmap-process.md +22 -9
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
- package/dist/docs/versioned/next/how-to/README.md +3 -0
- package/dist/docs/versioned/next/how-to/admin-add-model.md +41 -0
- package/dist/docs/versioned/next/how-to/admin-custom-home.md +31 -0
- package/dist/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
- package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
- package/dist/docs/versioned/next/how-to/circleci.md +173 -0
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +13 -10
- package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +46 -0
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
- package/dist/docs/versioned/next/how-to/websocket-integration.md +2 -1
- package/dist/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/README.md +31 -17
- package/dist/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
- package/dist/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
- package/dist/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/admin-only.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
- package/dist/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
- package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
- package/dist/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
- package/dist/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
- package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
- package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
- package/dist/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
- package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
- package/dist/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
- package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
- package/dist/docs/versioned/next/reference/README.md +6 -1
- package/dist/docs/versioned/next/reference/admin-config.md +51 -0
- package/dist/docs/versioned/next/reference/ai.md +15 -1
- package/dist/docs/versioned/next/reference/api.md +18 -0
- package/dist/docs/versioned/next/reference/comms.md +88 -7
- package/dist/docs/versioned/next/reference/environment-variables.md +9 -1
- package/dist/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +7 -0
- package/dist/docs/versioned/next/reference/syncdb.md +30 -3
- package/dist/docs/versioned/next/tasks/README.md +21 -10
- package/dist/docs/versioned/next/tasks/admin-improvements.md +189 -353
- package/dist/docs/versioned/next/tasks/admin-only.md +2 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +30 -24
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
- package/dist/docs/versioned/next/tasks/consent-forms.md +2 -0
- package/dist/docs/versioned/next/tasks/feature-flags.md +2 -0
- package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
- package/dist/docs/versioned/next/tasks/model-router-actions.md +2 -0
- package/dist/docs/versioned/next/tasks/model-router-mcp.md +27 -27
- package/dist/docs/versioned/next/tasks/offline-mode.md +2 -0
- package/dist/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
- package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
- package/dist/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
- package/dist/docs/versioned/next/tasks/upgrade-banner.md +2 -0
- package/dist/index.js +24 -55
- package/dist/index.js.map +1 -1
- package/dist/local/localServer.d.ts.map +1 -1
- package/dist/local/localServer.js +9 -8
- package/dist/local/localServer.js.map +1 -1
- package/dist/local/localTools.d.ts +1 -1
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +44 -45
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +9 -2
- package/dist/resources.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +4 -2
- package/dist/tools.js.map +1 -1
- package/package.json +7 -3
- package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
- package/src/docs/resources/overview.md +6 -1
- package/src/docs/resources/rtk.md +3 -1
- package/src/docs/resources/syncdb.md +43 -0
- package/src/docs/upgrades/56.0.0-beta.2.md +34 -0
- package/src/docs/upgrades/57.0.0.md +49 -0
- package/src/docs/upgrades/57.1.0.md +126 -0
- package/src/docs/versioned/next/README.md +15 -3
- package/src/docs/versioned/next/explanation/README.md +3 -0
- package/src/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
- package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
- package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
- package/src/docs/versioned/next/explanation/authentication.md +51 -58
- package/src/docs/versioned/next/explanation/local-first-data.md +52 -0
- package/src/docs/versioned/next/explanation/loop-engineering.md +117 -0
- package/src/docs/versioned/next/explanation/repository-settings.md +8 -0
- package/src/docs/versioned/next/explanation/roadmap-process.md +22 -9
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
- package/src/docs/versioned/next/how-to/README.md +3 -0
- package/src/docs/versioned/next/how-to/admin-add-model.md +41 -0
- package/src/docs/versioned/next/how-to/admin-custom-home.md +31 -0
- package/src/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
- package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
- package/src/docs/versioned/next/how-to/circleci.md +173 -0
- package/src/docs/versioned/next/how-to/configure-better-auth.md +13 -10
- package/src/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
- package/src/docs/versioned/next/how-to/install-agent-skills.md +46 -0
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
- package/src/docs/versioned/next/how-to/websocket-integration.md +2 -1
- package/src/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
- package/src/docs/versioned/next/implementationPlans/README.md +31 -17
- package/src/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
- package/src/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
- package/src/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
- package/src/docs/versioned/next/implementationPlans/admin-only.md +3 -1
- package/src/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
- package/src/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
- package/src/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
- package/src/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
- package/src/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
- package/src/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
- package/src/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
- package/src/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
- package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
- package/src/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
- package/src/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
- package/src/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
- package/src/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
- package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
- package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
- package/src/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
- package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
- package/src/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
- package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
- package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
- package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
- package/src/docs/versioned/next/reference/README.md +6 -1
- package/src/docs/versioned/next/reference/admin-config.md +51 -0
- package/src/docs/versioned/next/reference/ai.md +15 -1
- package/src/docs/versioned/next/reference/api.md +18 -0
- package/src/docs/versioned/next/reference/comms.md +88 -7
- package/src/docs/versioned/next/reference/environment-variables.md +9 -1
- package/src/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
- package/src/docs/versioned/next/reference/mcp-server.md +7 -0
- package/src/docs/versioned/next/reference/syncdb.md +30 -3
- package/src/docs/versioned/next/tasks/README.md +21 -10
- package/src/docs/versioned/next/tasks/admin-improvements.md +189 -353
- package/src/docs/versioned/next/tasks/admin-only.md +2 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
- package/src/docs/versioned/next/tasks/comms-abstraction.md +30 -24
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
- package/src/docs/versioned/next/tasks/consent-forms.md +2 -0
- package/src/docs/versioned/next/tasks/feature-flags.md +2 -0
- package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
- package/src/docs/versioned/next/tasks/model-router-actions.md +2 -0
- package/src/docs/versioned/next/tasks/model-router-mcp.md +27 -27
- package/src/docs/versioned/next/tasks/offline-mode.md +2 -0
- package/src/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
- package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
- package/src/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
- package/src/docs/versioned/next/tasks/upgrade-banner.md +2 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
- package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
- 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
|
|
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 {
|
|
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:
|
|
379
|
-
// biome-ignore lint/suspicious/noExplicitAny:
|
|
402
|
+
// noExplicitAny: User model type mismatch
|
|
403
|
+
// biome-ignore lint/suspicious/noExplicitAny: User model type mismatch
|
|
380
404
|
userModel: User as any,
|
|
381
|
-
})
|
|
382
|
-
|
|
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 =
|
|
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,
|
|
931
|
-
import {
|
|
932
|
-
import {
|
|
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 =
|
|
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
|
-
<
|
|
984
|
-
<
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
1010
|
-
const
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
}
|
|
1030
|
-
|
|
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
|
-
<
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
|
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 {
|
|
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
|
|
1451
|
+
const betterAuth = generateBetterAuthSlice({authClient: betterAuthClient});
|
|
1315
1452
|
|
|
1316
|
-
export const
|
|
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
|
-
|
|
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
|
-
...
|
|
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: (
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
-
|
|
2690
|
+
- Better Auth login (email/password via \`@terreno/ui\` LoginScreen)
|
|
2502
2691
|
- Tab-based navigation with Home and Profile screens
|
|
2503
|
-
-
|
|
2504
|
-
- RTK Query SDK
|
|
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
|
|