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