@terreno/mcp 57.19.2 → 57.20.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/CHANGELOG.md +393 -0
- package/README.md +32 -269
- package/dist/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +77 -122
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/resources/overview.md +4 -2
- package/dist/docs/resources/patterns.md +237 -176
- package/dist/docs/resources/ui.md +48 -17
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/dist/docs/upgrades/57.20.0.md +238 -0
- package/dist/docs/versioned/next/approval-rubric.md +30 -12
- package/dist/docs/versioned/next/explanation/README.md +5 -0
- package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
- package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/dist/docs/versioned/next/explanation/authentication.md +45 -1
- package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/dist/docs/versioned/next/explanation/organizations.md +37 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/dist/docs/versioned/next/how-to/README.md +7 -2
- package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
- package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/dist/docs/versioned/next/how-to/circleci.md +15 -5
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/dist/docs/versioned/next/reference/README.md +1 -0
- package/dist/docs/versioned/next/reference/account-security.md +700 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/dist/docs/versioned/next/reference/api.md +461 -35
- package/dist/docs/versioned/next/reference/cli.md +1 -1
- package/dist/docs/versioned/next/reference/comms.md +8 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
- package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/dist/docs/versioned/next/reference/syncdb.md +8 -1
- package/dist/docs/versioned/next/reference/ui.md +1271 -27
- package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/dist/docs/versioned/next/tutorials/README.md +4 -0
- package/dist/httpApp.d.ts +14 -0
- package/dist/httpApp.d.ts.map +1 -0
- package/dist/httpApp.js +99 -0
- package/dist/httpApp.js.map +1 -0
- package/dist/index.js +8 -90
- package/dist/index.js.map +1 -1
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +44 -14
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/metro/metroDevSession.d.ts +18 -0
- package/dist/local/metro/metroDevSession.d.ts.map +1 -1
- package/dist/local/metro/metroDevSession.js +42 -3
- package/dist/local/metro/metroDevSession.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +3 -13
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/local/tools/databaseSchema.d.ts.map +1 -1
- package/dist/local/tools/databaseSchema.js +3 -13
- package/dist/local/tools/databaseSchema.js.map +1 -1
- package/dist/local/tools/localMongo.d.ts +10 -0
- package/dist/local/tools/localMongo.d.ts.map +1 -0
- package/dist/local/tools/localMongo.js +30 -0
- package/dist/local/tools/localMongo.js.map +1 -0
- package/dist/local/tools/runtime.d.ts +18 -2
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +75 -20
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +11 -0
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +15 -3
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/rateLimit.d.ts +35 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +112 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +42 -60
- package/dist/tools.js.map +1 -1
- package/package.json +4 -3
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/resources/overview.md +4 -2
- package/src/docs/resources/patterns.md +237 -176
- package/src/docs/resources/ui.md +48 -17
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/src/docs/upgrades/57.20.0.md +238 -0
- package/src/docs/versioned/next/approval-rubric.md +30 -12
- package/src/docs/versioned/next/explanation/README.md +5 -0
- package/src/docs/versioned/next/explanation/account-linking.md +169 -0
- package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/src/docs/versioned/next/explanation/authentication.md +45 -1
- package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/src/docs/versioned/next/explanation/organizations.md +37 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/src/docs/versioned/next/how-to/README.md +7 -2
- package/src/docs/versioned/next/how-to/audit-log.md +22 -3
- package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/src/docs/versioned/next/how-to/circleci.md +15 -5
- package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/src/docs/versioned/next/reference/README.md +1 -0
- package/src/docs/versioned/next/reference/account-security.md +700 -0
- package/src/docs/versioned/next/reference/admin-backend.md +3 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/src/docs/versioned/next/reference/api.md +461 -35
- package/src/docs/versioned/next/reference/cli.md +1 -1
- package/src/docs/versioned/next/reference/comms.md +8 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/src/docs/versioned/next/reference/environment-variables.md +9 -3
- package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/src/docs/versioned/next/reference/syncdb.md +8 -1
- package/src/docs/versioned/next/reference/ui.md +1271 -27
- package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/src/docs/versioned/next/tutorials/README.md +4 -0
package/dist/bootstrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gCAAgC,EAChC,8BAA8B,EAE9B,4BAA4B,EAC5B,iCAAiC,GAClC,MAAM,wBAAwB,CAAC;AAQhC,MAAM,CAAC,MAAM,cAAc,GAAW;IACpC;QACE,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
React Native UI component library (a large component library):
|
|
4
4
|
|
|
5
|
-
- **Layout**: Box, Page, SplitPage, Card
|
|
5
|
+
- **Layout**: Box, Page, ListDetailLayout, SplitPage, Card
|
|
6
6
|
- **Forms**: TextField, SelectField, DateTimeField, CheckBox
|
|
7
7
|
- **Display**: Text, Heading, Badge, DataTable
|
|
8
8
|
- **Actions**: Button, IconButton, Link
|
|
@@ -41,8 +41,10 @@ New apps: **syncdb + Better Auth** for data and auth; **rtk** for generated SDK
|
|
|
41
41
|
|
|
42
42
|
Use the hosted Terreno MCP for documentation and generators, and `terreno-mcp-local` for package
|
|
43
43
|
versions, read-only Mongo inspection, merged logs, client state, and Metro/CDP runtime tools.
|
|
44
|
-
Configure both with
|
|
45
|
-
|
|
44
|
+
Configure both with
|
|
45
|
+
[Set up Terreno MCP](https://github.com/TerrenoLabs/terreno/blob/master/docs/how-to/set-up-terreno-mcp.md);
|
|
46
|
+
follow [Debug with MCP](https://github.com/TerrenoLabs/terreno/blob/master/docs/how-to/debug-with-mcp.md)
|
|
47
|
+
for the diagnostic workflow. The `terreno` CLI exposes the same
|
|
46
48
|
local tools for terminal use.
|
|
47
49
|
|
|
48
50
|
## Development
|
|
@@ -1,216 +1,269 @@
|
|
|
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
|
+
|
|
3
8
|
## Backend Patterns
|
|
4
9
|
|
|
5
10
|
### Model Definition
|
|
6
11
|
|
|
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
|
+
|
|
7
16
|
```typescript
|
|
17
|
+
import {syncPlugin} from "@terreno/api";
|
|
8
18
|
import mongoose from "mongoose";
|
|
9
|
-
import {
|
|
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
|
-
}
|
|
19
|
+
import type {TodoDocument, TodoModel} from "../types/models/todoTypes";
|
|
20
|
+
import {addDefaultPlugins} from "./modelPlugins";
|
|
22
21
|
|
|
23
22
|
const todoSchema = new mongoose.Schema<TodoDocument, TodoModel>(
|
|
24
23
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
_id: {
|
|
25
|
+
default: (): string => new mongoose.Types.ObjectId().toHexString(),
|
|
26
|
+
description: "The document id (String so offline sync clients can mint ids)",
|
|
27
|
+
type: String,
|
|
28
|
+
},
|
|
29
|
+
completed: {
|
|
30
|
+
default: false,
|
|
31
|
+
description: "Whether the todo item has been completed",
|
|
32
|
+
type: Boolean,
|
|
33
|
+
},
|
|
34
|
+
ownerId: {
|
|
35
|
+
description: "The user who owns this todo",
|
|
36
|
+
ref: "User",
|
|
37
|
+
required: true,
|
|
38
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
39
|
+
},
|
|
40
|
+
title: {
|
|
41
|
+
description: "The title of the todo item",
|
|
42
|
+
required: true,
|
|
43
|
+
trim: true,
|
|
44
|
+
type: String,
|
|
45
|
+
},
|
|
28
46
|
},
|
|
29
|
-
{
|
|
30
|
-
strict: "throw",
|
|
31
|
-
toJSON: { virtuals: true },
|
|
32
|
-
toObject: { virtuals: true },
|
|
33
|
-
}
|
|
47
|
+
{strict: "throw", toJSON: {virtuals: true}, toObject: {virtuals: true}}
|
|
34
48
|
);
|
|
35
49
|
|
|
50
|
+
// addDefaultPlugins applies createdUpdatedPlugin, isDeletedPlugin, findExactlyOne, findOneOrNone.
|
|
36
51
|
addDefaultPlugins(todoSchema);
|
|
37
|
-
|
|
38
|
-
todoSchema.
|
|
39
|
-
return this.find({ ownerId });
|
|
40
|
-
};
|
|
52
|
+
// Stamps a per-stream _syncSeq on every write; required by the router's sync config.
|
|
53
|
+
todoSchema.plugin(syncPlugin);
|
|
41
54
|
|
|
42
55
|
export const Todo = mongoose.model<TodoDocument, TodoModel>("Todo", todoSchema);
|
|
43
56
|
```
|
|
44
57
|
|
|
45
58
|
### Route Setup
|
|
46
59
|
|
|
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
|
+
|
|
47
63
|
```typescript
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
};
|
|
64
|
+
import {modelRouter, OwnerQueryFilter, Permissions} from "@terreno/api";
|
|
65
|
+
import {Todo} from "../models/todo";
|
|
66
|
+
import type {UserDocument} from "../types/models/userTypes";
|
|
67
|
+
|
|
68
|
+
export const todoRouter = modelRouter("/todos", Todo, {
|
|
69
|
+
permissions: {
|
|
70
|
+
create: [Permissions.IsAuthenticated],
|
|
71
|
+
delete: [Permissions.IsOwner],
|
|
72
|
+
list: [Permissions.IsAuthenticated],
|
|
73
|
+
read: [Permissions.IsOwner],
|
|
74
|
+
update: [Permissions.IsOwner],
|
|
75
|
+
},
|
|
76
|
+
preCreate: (body, req) => ({
|
|
77
|
+
...body,
|
|
78
|
+
ownerId: (req.user as unknown as UserDocument)?._id,
|
|
79
|
+
}),
|
|
80
|
+
queryFields: ["completed", "ownerId"],
|
|
81
|
+
queryFilter: OwnerQueryFilter,
|
|
82
|
+
sort: "-created",
|
|
83
|
+
sync: {scope: {type: "owner"}},
|
|
84
|
+
});
|
|
78
85
|
```
|
|
79
86
|
|
|
80
87
|
### Custom Endpoints
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
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();
|
|
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.
|
|
108
91
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
```typescript
|
|
93
|
+
import {APIError, modelRouter, Permissions, z} from "@terreno/api";
|
|
94
|
+
|
|
95
|
+
export const todoRouter = modelRouter("/todos", Todo, {
|
|
96
|
+
// ...permissions, sync, and the rest from Route Setup
|
|
97
|
+
collectionActions: {
|
|
98
|
+
bulkComplete: {
|
|
99
|
+
body: z.object({ids: z.array(z.string()).min(1)}).strict(),
|
|
100
|
+
handler: async ({body, user}) => {
|
|
101
|
+
const result = await Todo.updateMany(
|
|
102
|
+
{_id: {$in: body.ids}, ownerId: user?._id},
|
|
103
|
+
{$set: {completed: true}}
|
|
104
|
+
);
|
|
105
|
+
return {matched: result.matchedCount, modified: result.modifiedCount};
|
|
106
|
+
},
|
|
107
|
+
method: "POST",
|
|
108
|
+
permissions: [Permissions.IsAuthenticated],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
instanceActions: {
|
|
112
|
+
markComplete: {
|
|
113
|
+
handler: async ({doc}) => {
|
|
114
|
+
if (doc.completed) {
|
|
115
|
+
throw new APIError({status: 409, title: "Todo is already completed"});
|
|
116
|
+
}
|
|
117
|
+
doc.completed = true;
|
|
118
|
+
await doc.save();
|
|
119
|
+
return doc;
|
|
120
|
+
},
|
|
121
|
+
method: "POST",
|
|
122
|
+
permissions: [Permissions.IsOwner],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
115
126
|
```
|
|
116
127
|
|
|
117
128
|
## Frontend Patterns
|
|
118
129
|
|
|
119
130
|
### Screen with Data Fetching
|
|
120
131
|
|
|
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
|
+
|
|
121
137
|
```typescript
|
|
122
|
-
import
|
|
123
|
-
import {
|
|
124
|
-
import {
|
|
138
|
+
import {generateMutationId} from "@terreno/syncdb";
|
|
139
|
+
import {SyncDbProvider, useMutate, useQuery} from "@terreno/syncdb/react";
|
|
140
|
+
import {Box, Button, Card, CheckBox, Heading, IconButton, Text, TextField} from "@terreno/ui";
|
|
141
|
+
import {DateTime} from "luxon";
|
|
142
|
+
import type React from "react";
|
|
143
|
+
import {useCallback, useState} from "react";
|
|
144
|
+
import {useSyncDbReady} from "@/hooks/useSyncDbReady";
|
|
145
|
+
import {syncDb} from "@/store/syncdb";
|
|
146
|
+
|
|
147
|
+
interface Todo {
|
|
148
|
+
_id: string;
|
|
149
|
+
completed?: boolean;
|
|
150
|
+
created: string;
|
|
151
|
+
ownerId: string;
|
|
152
|
+
title: string;
|
|
153
|
+
}
|
|
125
154
|
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
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");
|
|
130
160
|
|
|
131
|
-
const handleCreate = useCallback(
|
|
132
|
-
|
|
161
|
+
const handleCreate = useCallback((): void => {
|
|
162
|
+
const title = newTitle.trim();
|
|
163
|
+
if (!title || !isSyncDbReady) {
|
|
133
164
|
return;
|
|
134
165
|
}
|
|
166
|
+
// Mint the id locally so the optimistic row is addressable before the server acks it.
|
|
167
|
+
const id = generateMutationId();
|
|
168
|
+
create({data: {_id: id, completed: false, created: DateTime.now().toISO(), title}, id});
|
|
169
|
+
setNewTitle("");
|
|
170
|
+
}, [create, isSyncDbReady, newTitle]);
|
|
171
|
+
|
|
172
|
+
const handleToggle = useCallback(
|
|
173
|
+
(todo: Todo): void => {
|
|
174
|
+
update({data: {completed: !todo.completed}, id: todo._id});
|
|
175
|
+
},
|
|
176
|
+
[update]
|
|
177
|
+
);
|
|
135
178
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}, [newTitle, createTodo]);
|
|
143
|
-
|
|
144
|
-
if (isLoading) {
|
|
145
|
-
return (
|
|
146
|
-
<Page navigation={undefined}>
|
|
147
|
-
<Box flex={1} alignItems="center" justifyContent="center">
|
|
148
|
-
<Text>Loading...</Text>
|
|
149
|
-
</Box>
|
|
150
|
-
</Page>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (error) {
|
|
155
|
-
return (
|
|
156
|
-
<Page navigation={undefined}>
|
|
157
|
-
<Box flex={1} alignItems="center" justifyContent="center" gap={2}>
|
|
158
|
-
<Text color="error500">Failed to load todos</Text>
|
|
159
|
-
<Button text="Retry" onClick={refetch} />
|
|
160
|
-
</Box>
|
|
161
|
-
</Page>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
179
|
+
const handleDelete = useCallback(
|
|
180
|
+
(id: string): void => {
|
|
181
|
+
remove({id});
|
|
182
|
+
},
|
|
183
|
+
[remove]
|
|
184
|
+
);
|
|
164
185
|
|
|
165
186
|
return (
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
<Box gap={3} padding={4} testID="todos-screen">
|
|
188
|
+
<Heading size="lg">Todos</Heading>
|
|
189
|
+
<Card>
|
|
190
|
+
<Box gap={3}>
|
|
191
|
+
<TextField
|
|
192
|
+
onChange={setNewTitle}
|
|
193
|
+
onEnter={handleCreate}
|
|
194
|
+
placeholder="New todo"
|
|
195
|
+
testID="todos-title-input"
|
|
196
|
+
title="New Todo"
|
|
197
|
+
value={newTitle}
|
|
198
|
+
/>
|
|
199
|
+
<Button
|
|
200
|
+
disabled={!isSyncDbReady || !newTitle.trim()}
|
|
201
|
+
iconName="plus"
|
|
202
|
+
onClick={handleCreate}
|
|
203
|
+
testID="todos-create-button"
|
|
204
|
+
text="Add Todo"
|
|
205
|
+
/>
|
|
206
|
+
</Box>
|
|
207
|
+
</Card>
|
|
208
|
+
{todos.length === 0 ? <Text testID="todos-empty-state">No todos yet</Text> : null}
|
|
209
|
+
{todos.map((todo) => (
|
|
210
|
+
<Card key={todo._id} testID={`todo-item-${todo._id}`}>
|
|
211
|
+
<Box alignItems="center" direction="row" gap={2}>
|
|
212
|
+
<Box onClick={() => handleToggle(todo)} testID={`todo-toggle-${todo._id}`}>
|
|
213
|
+
<CheckBox selected={Boolean(todo.completed)} size="md" />
|
|
214
|
+
</Box>
|
|
215
|
+
<Box flex="grow">
|
|
216
|
+
<Text>{todo.title}</Text>
|
|
176
217
|
</Box>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
218
|
+
<IconButton
|
|
219
|
+
accessibilityLabel="Delete todo"
|
|
220
|
+
iconName="trash"
|
|
221
|
+
onClick={() => handleDelete(todo._id)}
|
|
222
|
+
testID={`todo-delete-${todo._id}`}
|
|
223
|
+
variant="destructive"
|
|
182
224
|
/>
|
|
183
225
|
</Box>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
))}
|
|
188
|
-
</Box>
|
|
189
|
-
</ScrollView>
|
|
190
|
-
</Page>
|
|
226
|
+
</Card>
|
|
227
|
+
))}
|
|
228
|
+
</Box>
|
|
191
229
|
);
|
|
192
230
|
};
|
|
193
231
|
|
|
194
|
-
|
|
232
|
+
const TodosScreen: React.FC = () => (
|
|
233
|
+
<SyncDbProvider client={syncDb}>
|
|
234
|
+
<TodoList />
|
|
235
|
+
</SyncDbProvider>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
export default TodosScreen;
|
|
195
239
|
```
|
|
196
240
|
|
|
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
|
+
|
|
197
246
|
### Form with Validation
|
|
198
247
|
|
|
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
|
+
|
|
199
251
|
```typescript
|
|
200
|
-
import
|
|
201
|
-
import
|
|
202
|
-
import {
|
|
252
|
+
import {Box, Button, EmailField, TextField} from "@terreno/ui";
|
|
253
|
+
import type React from "react";
|
|
254
|
+
import {useCallback, useState} from "react";
|
|
255
|
+
import {usePatchMeMutation} from "@/store/sdk";
|
|
203
256
|
|
|
204
257
|
interface FormErrors {
|
|
205
258
|
name?: string;
|
|
206
259
|
email?: string;
|
|
207
260
|
}
|
|
208
261
|
|
|
209
|
-
const
|
|
210
|
-
const [name, setName] = useState("");
|
|
211
|
-
const [email, setEmail] = useState("");
|
|
262
|
+
const ProfileForm: React.FC = () => {
|
|
263
|
+
const [name, setName] = useState<string>("");
|
|
264
|
+
const [email, setEmail] = useState<string>("");
|
|
212
265
|
const [errors, setErrors] = useState<FormErrors>({});
|
|
213
|
-
const [
|
|
266
|
+
const [updateProfile, {isLoading}] = usePatchMeMutation();
|
|
214
267
|
|
|
215
268
|
const validate = useCallback((): boolean => {
|
|
216
269
|
const newErrors: FormErrors = {};
|
|
@@ -229,40 +282,43 @@ const CreateUserForm: React.FC = () => {
|
|
|
229
282
|
return Object.keys(newErrors).length === 0;
|
|
230
283
|
}, [name, email]);
|
|
231
284
|
|
|
232
|
-
const handleSubmit = useCallback(async () => {
|
|
285
|
+
const handleSubmit = useCallback(async (): Promise<void> => {
|
|
233
286
|
if (!validate()) {
|
|
234
287
|
return;
|
|
235
288
|
}
|
|
236
289
|
|
|
237
290
|
try {
|
|
238
|
-
await
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
setErrors(
|
|
291
|
+
await updateProfile({email, name}).unwrap();
|
|
292
|
+
} catch (error: unknown) {
|
|
293
|
+
const fields = (error as {data?: {fields?: FormErrors}}).data?.fields;
|
|
294
|
+
if (fields) {
|
|
295
|
+
setErrors(fields);
|
|
243
296
|
}
|
|
244
297
|
}
|
|
245
|
-
}, [name, email, validate,
|
|
298
|
+
}, [name, email, validate, updateProfile]);
|
|
246
299
|
|
|
247
300
|
return (
|
|
248
|
-
<Box
|
|
301
|
+
<Box gap={3} padding={4}>
|
|
249
302
|
<TextField
|
|
250
|
-
|
|
303
|
+
errorText={errors.name}
|
|
304
|
+
onChange={setName}
|
|
305
|
+
testID="profile-name-input"
|
|
306
|
+
title="Name"
|
|
251
307
|
value={name}
|
|
252
|
-
onChangeText={setName}
|
|
253
|
-
error={errors.name}
|
|
254
308
|
/>
|
|
255
309
|
<EmailField
|
|
256
|
-
|
|
310
|
+
errorText={errors.email}
|
|
311
|
+
onChange={setEmail}
|
|
312
|
+
testID="profile-email-input"
|
|
313
|
+
title="Email"
|
|
257
314
|
value={email}
|
|
258
|
-
onChangeText={setEmail}
|
|
259
|
-
error={errors.email}
|
|
260
315
|
/>
|
|
261
316
|
<Button
|
|
262
|
-
text="Create User"
|
|
263
|
-
onClick={handleSubmit}
|
|
264
|
-
loading={isLoading}
|
|
265
317
|
fullWidth
|
|
318
|
+
loading={isLoading}
|
|
319
|
+
onClick={handleSubmit}
|
|
320
|
+
testID="profile-save-button"
|
|
321
|
+
text="Save"
|
|
266
322
|
/>
|
|
267
323
|
</Box>
|
|
268
324
|
);
|
|
@@ -305,19 +361,24 @@ throw new APIError({
|
|
|
305
361
|
|
|
306
362
|
### Frontend
|
|
307
363
|
|
|
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
|
+
|
|
308
369
|
```typescript
|
|
309
370
|
try {
|
|
310
371
|
await someApiCall().unwrap();
|
|
311
|
-
} catch (
|
|
372
|
+
} catch (error: unknown) {
|
|
373
|
+
const data = (error as {data?: {detail?: string; fields?: Record<string, string>}}).data;
|
|
312
374
|
// Handle field-specific errors
|
|
313
|
-
if (
|
|
314
|
-
setErrors(
|
|
375
|
+
if (data?.fields) {
|
|
376
|
+
setErrors(data.fields);
|
|
315
377
|
return;
|
|
316
378
|
}
|
|
317
379
|
|
|
318
380
|
// Handle general errors
|
|
319
|
-
|
|
320
|
-
toast.error(message);
|
|
381
|
+
toast.error(data?.detail ?? "An error occurred");
|
|
321
382
|
}
|
|
322
383
|
```
|
|
323
384
|
|