@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,474 +1,185 @@
|
|
|
1
1
|
# How to Add WebSocket Integration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Push server events to a Terreno app over the socket `@terreno/syncdb` already opens. One app
|
|
4
|
+
keeps one Socket.io connection: sync deltas, feature-flag pushes, and your own events share it.
|
|
4
5
|
|
|
5
6
|
## Prerequisites
|
|
6
7
|
|
|
7
|
-
- Backend
|
|
8
|
-
- Frontend
|
|
9
|
-
-
|
|
8
|
+
- Backend registers `RealtimeApp` (and `SyncApp`) from `@terreno/api`
|
|
9
|
+
- Frontend creates a `SyncDb` client with `createSyncDb` and starts it after login
|
|
10
|
+
- MongoDB runs as a replica set (change streams power sync and flag pushes)
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Backend Setup
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
### 1. Keep a reference to `RealtimeApp`
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# socket.io-client is a peer dependency
|
|
18
|
-
bun install socket.io-client
|
|
19
|
-
``````
|
|
16
|
+
`RealtimeApp` attaches Socket.io to the server `TerrenoApp.start()` creates, authenticates each
|
|
17
|
+
socket (JWT and/or Better Auth), and joins it to these rooms:
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
| Room | Members |
|
|
20
|
+
|------|---------|
|
|
21
|
+
| `user:<userId>` | Every socket of that user |
|
|
22
|
+
| `authenticated` | Every signed-in socket |
|
|
23
|
+
| `admin` | Sockets of admin users |
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
Keep the instance so you can emit through `getIo()`:
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
import {
|
|
27
|
-
import {Server} from "socket.io";
|
|
28
|
-
import {setupServer} from "@terreno/api";
|
|
29
|
-
|
|
30
|
-
const app = setupServer({
|
|
31
|
-
userModel: User,
|
|
32
|
-
addRoutes: (router) => {
|
|
33
|
-
// Your routes
|
|
34
|
-
},
|
|
35
|
-
});
|
|
27
|
+
```typescript
|
|
28
|
+
import {RealtimeApp, SyncApp, TerrenoApp} from "@terreno/api";
|
|
36
29
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
cors: {
|
|
40
|
-
origin: process.env.FRONTEND_URL || "http://localhost:8081",
|
|
41
|
-
credentials: true,
|
|
42
|
-
},
|
|
43
|
-
transports: ["websocket"],
|
|
30
|
+
export const realtimeApp = new RealtimeApp({
|
|
31
|
+
betterAuth: {auth: betterAuthInstance, userModel: User},
|
|
44
32
|
});
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
// Verify JWT token
|
|
56
|
-
const decoded = verifyToken(token);
|
|
57
|
-
const user = await User.findById(decoded.sub);
|
|
58
|
-
|
|
59
|
-
if (!user) {
|
|
60
|
-
return next(new Error("User not found"));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
socket.data.user = user;
|
|
64
|
-
next();
|
|
65
|
-
} catch (error) {
|
|
66
|
-
next(new Error("Invalid token"));
|
|
67
|
-
}
|
|
68
|
-
});
|
|
34
|
+
new TerrenoApp({userModel: User})
|
|
35
|
+
.register(todoRouter)
|
|
36
|
+
.register(new SyncApp())
|
|
37
|
+
.register(realtimeApp)
|
|
38
|
+
.start();
|
|
39
|
+
```
|
|
69
40
|
|
|
70
|
-
|
|
71
|
-
io.on("connection", (socket) => {
|
|
72
|
-
const user = socket.data.user;
|
|
73
|
-
console.log(`User connected: ${user.email} (${socket.id})`);
|
|
74
|
-
|
|
75
|
-
// Join user-specific room
|
|
76
|
-
socket.join(`user:${user._id}`);
|
|
77
|
-
|
|
78
|
-
// Handle custom events
|
|
79
|
-
socket.on("subscribe:todos", () => {
|
|
80
|
-
socket.join("todos");
|
|
81
|
-
console.log(`${user.email} subscribed to todos`);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
socket.on("disconnect", (reason) => {
|
|
85
|
-
console.log(`User disconnected: ${user.email} (${reason})`);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
httpServer.listen(4000, () => {
|
|
90
|
-
console.log("Server listening on port 4000");
|
|
91
|
-
});
|
|
41
|
+
`getIo()` returns `null` until the server starts, so call it when you emit, not at import time.
|
|
92
42
|
|
|
93
|
-
|
|
94
|
-
``````
|
|
43
|
+
### 2. Emit events
|
|
95
44
|
|
|
96
|
-
|
|
45
|
+
Emit app events with names that do not start with `sync:` (the sync protocol owns that prefix):
|
|
97
46
|
|
|
98
|
-
|
|
99
|
-
import {
|
|
47
|
+
```typescript
|
|
48
|
+
import {realtimeApp} from "./realtime";
|
|
100
49
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
postCreate: (todo, req) => {
|
|
107
|
-
// Emit to all clients in the todos room
|
|
108
|
-
io.to("todos").emit("todo:created", {
|
|
109
|
-
id: todo._id,
|
|
110
|
-
title: todo.title,
|
|
111
|
-
ownerId: todo.ownerId,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Emit to specific user
|
|
115
|
-
io.to(`user:${todo.ownerId}`).emit("notification", {
|
|
116
|
-
type: "todo_created",
|
|
117
|
-
message: `Todo "${todo.title}" created`,
|
|
50
|
+
modelRouter("/announcements", Announcement, {
|
|
51
|
+
permissions: {create: [Permissions.IsAdmin] /* ... */},
|
|
52
|
+
postCreate: (announcement) => {
|
|
53
|
+
realtimeApp.getIo()?.to("authenticated").emit("announcementPublished", {
|
|
54
|
+
id: announcement._id,
|
|
118
55
|
});
|
|
119
56
|
},
|
|
120
57
|
});
|
|
121
|
-
``````
|
|
122
|
-
|
|
123
|
-
## Frontend Setup
|
|
124
58
|
|
|
125
|
-
|
|
59
|
+
// One user only:
|
|
60
|
+
realtimeApp.getIo()?.to(`user:${userId}`).emit("exportReady", {url});
|
|
61
|
+
```
|
|
126
62
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
``````json
|
|
130
|
-
{
|
|
131
|
-
"expo": {
|
|
132
|
-
"extra": {
|
|
133
|
-
"WEBSOCKET_URL": "wss://api.example.com",
|
|
134
|
-
"WEBSOCKETS_DEBUG": false
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
``````
|
|
63
|
+
Send ids or small hints, not documents the client may not be allowed to read; let the client
|
|
64
|
+
refetch through the API with its own permissions.
|
|
139
65
|
|
|
140
|
-
|
|
66
|
+
For feature flags, point `FeatureFlagsApp` at the same server and the client refreshes with no
|
|
67
|
+
extra code:
|
|
141
68
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
``````typescript
|
|
147
|
-
// store/tokenSource.ts: import once from the app entry, before the store is created
|
|
148
|
-
import {setTokenSource} from "@terreno/rtk";
|
|
149
|
-
import {betterAuthAdapter, bridgeBetterAuthReactClient} from "@terreno/syncdb";
|
|
150
|
-
import {authClient} from "@/lib/betterAuth";
|
|
151
|
-
|
|
152
|
-
setTokenSource(betterAuthAdapter(bridgeBetterAuthReactClient(authClient)));
|
|
153
|
-
``````
|
|
69
|
+
```typescript
|
|
70
|
+
new FeatureFlagsApp({liveUpdates: {socketIoServer: () => realtimeApp.getIo()}});
|
|
71
|
+
```
|
|
154
72
|
|
|
155
|
-
|
|
156
|
-
([syncdb adapter](../reference/syncdb.md#createsyncdb-configuration)).
|
|
157
|
-
|
|
158
|
-
With a token source, the hook:
|
|
73
|
+
## Frontend Setup
|
|
159
74
|
|
|
160
|
-
|
|
161
|
-
- calls `refresh()` once when the server rejects the token, then reconnects, and stops after a
|
|
162
|
-
second rejection (`onConnectError` fires). With no `refresh`, or a failed one, it stops at
|
|
163
|
-
once;
|
|
164
|
-
- reconnects with the new token when `onAuthChange` fires, and disconnects when there is no
|
|
165
|
-
token.
|
|
75
|
+
### 1. Listen in components with `useSocketEvent`
|
|
166
76
|
|
|
167
|
-
|
|
168
|
-
`
|
|
169
|
-
[Token source](../reference/legacy/rtk.md#token-source).
|
|
77
|
+
`useSocketEvent(event, handler, options?)` from `@terreno/syncdb/react` binds a listener on the
|
|
78
|
+
`SyncDbProvider` client's socket while the component is mounted:
|
|
170
79
|
|
|
171
|
-
|
|
80
|
+
```typescript
|
|
81
|
+
import {useApiQuery, useSocketEvent} from "@terreno/syncdb/react";
|
|
172
82
|
|
|
173
|
-
|
|
174
|
-
|
|
83
|
+
export const AnnouncementsBadge: React.FC = () => {
|
|
84
|
+
const {data, refetch} = useApiQuery<{data: Announcement[]}>({url: "/announcements"});
|
|
175
85
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
import {useSocketConnection, baseWebsocketsUrl} from "@terreno/rtk";
|
|
179
|
-
import {selectBetterAuthUserId} from "@terreno/rtk";
|
|
180
|
-
import {useSelector} from "react-redux";
|
|
181
|
-
import {useCallback} from "react";
|
|
182
|
-
import * as Sentry from "@sentry/react-native";
|
|
183
|
-
|
|
184
|
-
export const useWebSocket = () => {
|
|
185
|
-
const userId = useSelector(selectBetterAuthUserId); // JWT apps: useSelectCurrentUserId()
|
|
186
|
-
|
|
187
|
-
const {socket, isSocketConnected} = useSocketConnection({
|
|
188
|
-
baseUrl: baseWebsocketsUrl,
|
|
189
|
-
shouldConnect: !!userId,
|
|
190
|
-
onConnect: () => {
|
|
191
|
-
console.info("[WebSocket] Connected");
|
|
192
|
-
},
|
|
193
|
-
onDisconnect: () => {
|
|
194
|
-
console.warn("[WebSocket] Disconnected");
|
|
195
|
-
},
|
|
196
|
-
onConnectError: (error) => {
|
|
197
|
-
console.error("[WebSocket] Connection error:", error.message);
|
|
198
|
-
},
|
|
199
|
-
captureEvent: (eventName, data) => {
|
|
200
|
-
Sentry.captureMessage(eventName, {
|
|
201
|
-
level: "info",
|
|
202
|
-
extra: data,
|
|
203
|
-
});
|
|
204
|
-
},
|
|
86
|
+
useSocketEvent("announcementPublished", () => {
|
|
87
|
+
void refetch();
|
|
205
88
|
});
|
|
206
|
-
|
|
207
|
-
return {
|
|
89
|
+
|
|
90
|
+
return <Badge value={String(data?.data.length ?? 0)} />;
|
|
208
91
|
};
|
|
209
|
-
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- The handler can change on every render; the latest one runs and the listener is not rebound.
|
|
95
|
+
- Outside a `SyncDbProvider`, pass the client: `useSocketEvent(name, handler, {client: syncDb})`.
|
|
96
|
+
- `{client: null}` or `{enabled: false}` listens to nothing (for example while signed out).
|
|
210
97
|
|
|
211
98
|
#### Transports
|
|
212
99
|
|
|
213
|
-
|
|
100
|
+
The syncdb socket starts with HTTP long-polling and upgrades to WebSocket (`socketTransports: ["polling", "websocket"]`), so a restarting dev server does not fail the first handshake. Polling needs every request of a session to reach the same server process. Hosts without session affinity, such as Vercel Functions, need WebSocket only:
|
|
214
101
|
|
|
215
102
|
``````typescript
|
|
216
|
-
|
|
103
|
+
createSyncDb({
|
|
217
104
|
baseUrl,
|
|
218
|
-
|
|
219
|
-
|
|
105
|
+
collections: ["todos"],
|
|
106
|
+
socketTransports: ["websocket"],
|
|
220
107
|
});
|
|
221
108
|
``````
|
|
222
109
|
|
|
223
|
-
|
|
110
|
+
Cloud Run with `--session-affinity` works with the default. See [Run Terreno WebSockets on Vercel Functions](vercel-function-websockets.md).
|
|
224
111
|
|
|
225
|
-
|
|
112
|
+
Synced collections already update live through `useQuery` / `useEntity`; use socket events for
|
|
113
|
+
everything else.
|
|
226
114
|
|
|
227
|
-
###
|
|
115
|
+
### 2. Listen outside React with `onSocketEvent`
|
|
228
116
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
import {useWebSocket} from "@/hooks/useWebSocket";
|
|
232
|
-
import {useEffect, useCallback} from "react";
|
|
233
|
-
|
|
234
|
-
export const TodoListScreen = () => {
|
|
235
|
-
const {data: todos, refetch} = useGetTodosQuery();
|
|
236
|
-
const {socket} = useWebSocket();
|
|
237
|
-
|
|
238
|
-
// Subscribe to todo events
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!socket) return;
|
|
241
|
-
|
|
242
|
-
// Join the todos room
|
|
243
|
-
socket.emit("subscribe:todos");
|
|
244
|
-
|
|
245
|
-
// Handle todo created event
|
|
246
|
-
const handleTodoCreated = (todo) => {
|
|
247
|
-
console.info("New todo created:", todo);
|
|
248
|
-
refetch(); // Refresh the list
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
// Handle todo updated event
|
|
252
|
-
const handleTodoUpdated = (todo) => {
|
|
253
|
-
console.info("Todo updated:", todo);
|
|
254
|
-
refetch();
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
socket.on("todo:created", handleTodoCreated);
|
|
258
|
-
socket.on("todo:updated", handleTodoUpdated);
|
|
259
|
-
|
|
260
|
-
// Cleanup
|
|
261
|
-
return () => {
|
|
262
|
-
socket.off("todo:created", handleTodoCreated);
|
|
263
|
-
socket.off("todo:updated", handleTodoUpdated);
|
|
264
|
-
};
|
|
265
|
-
}, [socket, refetch]);
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<Page title="Todos">
|
|
269
|
-
{todos?.data.map((todo) => (
|
|
270
|
-
<TodoItem key={todo._id} todo={todo} />
|
|
271
|
-
))}
|
|
272
|
-
</Page>
|
|
273
|
-
);
|
|
274
|
-
};
|
|
275
|
-
``````
|
|
276
|
-
|
|
277
|
-
### 5. Show Connection Status (Optional)
|
|
117
|
+
```typescript
|
|
118
|
+
import {syncDb} from "@/store/syncdb";
|
|
278
119
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
import {Box, Text, Badge} from "@terreno/ui";
|
|
282
|
-
|
|
283
|
-
export const ConnectionStatus = () => {
|
|
284
|
-
const {isSocketConnected} = useWebSocket();
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<Box direction="row" gap={2} alignItems="center">
|
|
288
|
-
<Badge
|
|
289
|
-
variant={isSocketConnected.isConnected ? "success" : "error"}
|
|
290
|
-
text={isSocketConnected.isConnected ? "Connected" : "Disconnected"}
|
|
291
|
-
/>
|
|
292
|
-
{!isSocketConnected.isConnected && isSocketConnected.lastDisconnectedAt && (
|
|
293
|
-
<Text size="sm" color="secondaryDark">
|
|
294
|
-
Reconnecting...
|
|
295
|
-
</Text>
|
|
296
|
-
)}
|
|
297
|
-
</Box>
|
|
298
|
-
);
|
|
299
|
-
};
|
|
300
|
-
``````
|
|
301
|
-
|
|
302
|
-
## Advanced Patterns
|
|
303
|
-
|
|
304
|
-
### Optimistic Updates with Real-time Sync
|
|
305
|
-
|
|
306
|
-
``````typescript
|
|
307
|
-
const [updateTodo] = usePatchTodosMutation();
|
|
308
|
-
|
|
309
|
-
const handleToggle = useCallback(async (todo) => {
|
|
310
|
-
// Optimistic update
|
|
311
|
-
const completed = !todo.completed;
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
await updateTodo({
|
|
315
|
-
id: todo._id,
|
|
316
|
-
body: {completed},
|
|
317
|
-
}).unwrap();
|
|
318
|
-
|
|
319
|
-
// Server will emit event, triggering refetch for other clients
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Failed to update todo:", error);
|
|
322
|
-
// Revert optimistic update
|
|
323
|
-
}
|
|
324
|
-
}, [updateTodo]);
|
|
325
|
-
``````
|
|
326
|
-
|
|
327
|
-
### Room-based Subscriptions
|
|
328
|
-
|
|
329
|
-
``````typescript
|
|
330
|
-
// Backend: User joins project-specific room
|
|
331
|
-
socket.on("join:project", (projectId) => {
|
|
332
|
-
socket.join(`project:${projectId}`);
|
|
120
|
+
const unsubscribe = syncDb.onSocketEvent("exportReady", (payload) => {
|
|
121
|
+
console.info("export ready", payload);
|
|
333
122
|
});
|
|
123
|
+
// later
|
|
124
|
+
unsubscribe();
|
|
125
|
+
```
|
|
334
126
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// Frontend: Subscribe to project updates
|
|
339
|
-
useEffect(() => {
|
|
340
|
-
if (!socket || !projectId) return;
|
|
341
|
-
|
|
342
|
-
socket.emit("join:project", projectId);
|
|
343
|
-
socket.on("project:updated", handleProjectUpdate);
|
|
344
|
-
|
|
345
|
-
return () => {
|
|
346
|
-
socket.off("project:updated", handleProjectUpdate);
|
|
347
|
-
};
|
|
348
|
-
}, [socket, projectId]);
|
|
349
|
-
``````
|
|
127
|
+
Listeners stay bound across `stop()` / `start()` and reconnects; they fire only while the
|
|
128
|
+
socket is connected. Binding a reserved name (`sync:*`, `connect`, `disconnect`,
|
|
129
|
+
`connect_error`, …) throws.
|
|
350
130
|
|
|
351
|
-
###
|
|
131
|
+
### 3. Show connection status (optional)
|
|
352
132
|
|
|
353
|
-
|
|
354
|
-
// Backend: Track active users
|
|
355
|
-
const activeUsers = new Map();
|
|
356
|
-
|
|
357
|
-
io.on("connection", (socket) => {
|
|
358
|
-
const user = socket.data.user;
|
|
359
|
-
activeUsers.set(user._id, {socketId: socket.id, lastSeen: new Date()});
|
|
360
|
-
|
|
361
|
-
io.emit("presence:update", Array.from(activeUsers.keys()));
|
|
362
|
-
|
|
363
|
-
socket.on("disconnect", () => {
|
|
364
|
-
activeUsers.delete(user._id);
|
|
365
|
-
io.emit("presence:update", Array.from(activeUsers.keys()));
|
|
366
|
-
});
|
|
367
|
-
});
|
|
133
|
+
`useSyncStatus()` exposes the same socket's state:
|
|
368
134
|
|
|
369
|
-
|
|
370
|
-
|
|
135
|
+
```typescript
|
|
136
|
+
import {useSyncStatus} from "@terreno/syncdb/react";
|
|
137
|
+
import {Badge} from "@terreno/ui";
|
|
371
138
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
return () => {
|
|
380
|
-
socket.off("presence:update");
|
|
381
|
-
};
|
|
382
|
-
}, [socket]);
|
|
383
|
-
``````
|
|
384
|
-
|
|
385
|
-
## Debugging
|
|
386
|
-
|
|
387
|
-
Enable debug logging in your `app.json`:
|
|
388
|
-
|
|
389
|
-
``````json
|
|
390
|
-
{
|
|
391
|
-
"expo": {
|
|
392
|
-
"extra": {
|
|
393
|
-
"WEBSOCKETS_DEBUG": true
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
``````
|
|
398
|
-
|
|
399
|
-
This logs:
|
|
400
|
-
- Connection attempts
|
|
401
|
-
- Disconnection events with reasons
|
|
402
|
-
- Token refresh attempts
|
|
403
|
-
- Reconnection failures
|
|
139
|
+
export const ConnectionStatus: React.FC = () => {
|
|
140
|
+
const {isOnline} = useSyncStatus();
|
|
141
|
+
return <Badge status={isOnline ? "success" : "error"} value={isOnline ? "Online" : "Offline"} />;
|
|
142
|
+
};
|
|
143
|
+
```
|
|
404
144
|
|
|
405
145
|
## Troubleshooting
|
|
406
146
|
|
|
407
|
-
###
|
|
147
|
+
### Events never arrive
|
|
408
148
|
|
|
409
|
-
|
|
149
|
+
1. Confirm the client has started: `syncDb.getSyncStatus().isOnline` is `true`.
|
|
150
|
+
2. Confirm the server emits on `realtimeApp.getIo()`, not a second `new Server(...)`; a second
|
|
151
|
+
server has no syncdb clients connected.
|
|
152
|
+
3. Check the room. `user:<id>` uses the id the socket authenticated as.
|
|
153
|
+
4. Check the event name matches exactly on both sides.
|
|
410
154
|
|
|
411
|
-
|
|
412
|
-
1. Ensure the token source returns a valid token: `await getTokenSource()?.getToken()` (or
|
|
413
|
-
`getAuthToken()` on a JWT app with no source registered)
|
|
414
|
-
2. Check backend token verification logic
|
|
415
|
-
3. Enable `WEBSOCKETS_DEBUG` to see token status
|
|
155
|
+
### Two socket connections in the network panel
|
|
416
156
|
|
|
417
|
-
|
|
157
|
+
Something still opens its own Socket.io client. Remove it and listen through
|
|
158
|
+
`useSocketEvent` / `onSocketEvent`. The example app's Playwright suite asserts one connection
|
|
159
|
+
(`example-frontend/e2e/socket-flags.spec.ts`).
|
|
418
160
|
|
|
419
|
-
|
|
161
|
+
### Authentication errors
|
|
420
162
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
- Toast notifications inform users after 9+ seconds
|
|
425
|
-
- On Vercel Functions, every socket closes at the Function max duration (300s by default); see [reconnects](vercel-function-websockets.md#reconnects)
|
|
163
|
+
The socket sends the token from the `authProvider` passed to `createSyncDb` on every connection
|
|
164
|
+
attempt. An auth rejection pauses sync (`useSyncStatus().paused === "auth"`) until the session
|
|
165
|
+
refreshes. On Vercel Functions, every socket closes at the Function max duration (300s by default); clients reconnect automatically. See [syncdb reference](../reference/syncdb.md) and [Run Terreno WebSockets on Vercel Functions](vercel-function-websockets.md#reconnects).
|
|
426
166
|
|
|
427
167
|
### "Session ID unknown" or a 400 on `transport=polling`
|
|
428
168
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
**Solution:** Pass `transports: ["websocket"]` to `useSocketConnection` and `socketTransports: ["websocket"]` to `createSyncDb`, or enable session affinity on the host.
|
|
432
|
-
|
|
433
|
-
### Events not received
|
|
434
|
-
|
|
435
|
-
**Cause:** Not subscribed to the correct room, or event name mismatch.
|
|
436
|
-
|
|
437
|
-
**Solution:**
|
|
438
|
-
1. Verify room subscription: `socket.emit("subscribe:todos")`
|
|
439
|
-
2. Check event names match between backend and frontend
|
|
440
|
-
3. Use `socket.onAny((event, ...args) => console.log(event, args))` to debug
|
|
441
|
-
|
|
442
|
-
### Token refresh loops
|
|
443
|
-
|
|
444
|
-
**Cause:** Backend rejecting refresh token.
|
|
445
|
-
|
|
446
|
-
**Solution:**
|
|
447
|
-
1. Check `REFRESH_TOKEN_SECRET` matches between backend and frontend config
|
|
448
|
-
2. Verify refresh token hasn't expired
|
|
449
|
-
3. Check Sentry/logs for token verification errors
|
|
450
|
-
|
|
451
|
-
## Environment Variables
|
|
169
|
+
Polling requests for one session reached different server instances (no session affinity). Pass `socketTransports: ["websocket"]` to `createSyncDb`, or enable session affinity on the host.
|
|
452
170
|
|
|
453
|
-
| Variable | Required | Description |
|
|
454
|
-
|----------|----------|-------------|
|
|
455
|
-
| `WEBSOCKET_URL` | Yes | WebSocket server URL (e.g., `wss://api.example.com`) |
|
|
456
|
-
| `WEBSOCKETS_DEBUG` | No | Enable debug logging (default: false) |
|
|
457
|
-
| `TOKEN_SECRET` | Yes | JWT secret for token verification (backend) |
|
|
458
|
-
| `REFRESH_TOKEN_SECRET` | Yes | Refresh token secret (backend) |
|
|
459
171
|
|
|
460
172
|
## Security Considerations
|
|
461
173
|
|
|
462
|
-
1.
|
|
463
|
-
2.
|
|
464
|
-
3.
|
|
465
|
-
4.
|
|
466
|
-
5. **Input validation:** Validate all event data on the server
|
|
174
|
+
1. Use `https://` / `wss://` in production.
|
|
175
|
+
2. Emit per-user data only to `user:<id>` rooms.
|
|
176
|
+
3. Keep payloads to ids and hints; clients fetch details through permission-checked APIs.
|
|
177
|
+
4. Validate any data your handlers trust.
|
|
467
178
|
|
|
468
179
|
## Related Documentation
|
|
469
180
|
|
|
470
|
-
- [@terreno/syncdb
|
|
471
|
-
- [
|
|
472
|
-
- [Authentication
|
|
181
|
+
- [@terreno/syncdb reference](../reference/syncdb.md) — `onSocketEvent`, `useSocketEvent`
|
|
182
|
+
- [Feature flags reference](../reference/feature-flags.md) — live flag updates
|
|
183
|
+
- [Authentication architecture](../explanation/authentication.md)
|
|
473
184
|
- [Run Terreno WebSockets on Vercel Functions](vercel-function-websockets.md) — Vercel's WebSocket beta: transports, reconnects, cross-instance state
|
|
474
|
-
- [Socket.io
|
|
185
|
+
- [Socket.io documentation](https://socket.io/docs/v4/)
|
|
@@ -103,7 +103,7 @@ All decisions resolved by the maintainer on 2026-08-09:
|
|
|
103
103
|
| [orgs-and-teams](orgs-and-teams.md) | Superseded by [org-management-ui](org-management-ui.md) | — |
|
|
104
104
|
| [org-management-ui](org-management-ui.md) | Draft — awaiting approval | [tasks](../tasks/org-management-ui.md) |
|
|
105
105
|
| [billing-stripe](billing-stripe.md) | Draft | [tasks](../tasks/billing-stripe.md) |
|
|
106
|
-
| [native-module-baseline](native-module-baseline.md) |
|
|
106
|
+
| [native-module-baseline](native-module-baseline.md) | In progress | [tasks](../tasks/native-module-baseline.md) |
|
|
107
107
|
| [rbac-permissions](rbac-permissions.md) | Complete (PR #932) | — |
|
|
108
108
|
| [notification-center](notification-center.md) | Approved | [tasks](../tasks/notification-center.md) |
|
|
109
109
|
| [framework-audit-log](framework-audit-log.md) | Approved | [tasks](../tasks/framework-audit-log.md) |
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
**Created:** 2026-09-01
|
|
8
8
|
**Grow run:** https://cursor.com/agents/bc-47350376-6566-4c5a-aee2-e6a28698de61
|
|
9
9
|
|
|
10
|
+
> **Terreno 58 follow-up (done):** the dual-run window closed. `useAdminApi`, admin
|
|
11
|
+
> `injectEndpoints`, and the admin `api` prop were removed (umbrella issue #1252).
|
|
12
|
+
> ObjectId and API-only admin CRUD now use the `AdminProvider` fetch client through
|
|
13
|
+
> `useAdminModelApi`. The plan below is kept as written for history.
|
|
14
|
+
|
|
10
15
|
## Goal
|
|
11
16
|
|
|
12
17
|
Move **built-in admin model list/read/create/update/delete** for collections that already use **String `_id`** onto **windowed local-first syncdb**, with **REST remaining the membership source** (search, sort, pagination, RBAC). Remaining admin RPC (config, scripts, roles, comms, AI, consent, documents, version-config, background-tasks) leaves RTK `injectEndpoints` for a **tiny native `fetch` wrapper** with **host-injected auth**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Implementation Plan: Native module baseline for the next major release
|
|
2
2
|
|
|
3
|
-
**Status:**
|
|
3
|
+
**Status:** In progress (Phase 1 and diagnostics story landed on `release-58.0.0`; EAS builds pending)
|
|
4
4
|
**Roadmap issue:** https://github.com/TerrenoLabs/terreno/issues/1026
|
|
5
5
|
**Priority:** High
|
|
6
6
|
**Effort:** Small batch (code) — coordination cost is the release itself
|
|
@@ -28,12 +28,36 @@ JS/OTA update against the same binary.
|
|
|
28
28
|
|
|
29
29
|
| Question | Decision |
|
|
30
30
|
|----------|----------|
|
|
31
|
-
| Manifest (D7, resolved) | `@stripe/stripe-react-native`, `react-native-purchases` + `react-native-purchases-ui`, `expo-device`, `expo-crypto`, `expo-local-authentication`, `expo-system-ui`, `react-native-otp-verify` |
|
|
31
|
+
| Manifest (D7, resolved) | `@stripe/stripe-react-native`, `react-native-purchases` + `react-native-purchases-ui`, `expo-device`, `expo-crypto`, `expo-local-authentication`, `expo-system-ui`, `react-native-otp-verify` (dropped at implementation; see below) |
|
|
32
32
|
| Where dependencies live | Root catalog entries; linked in `example-frontend` and `demo` app manifests; `@terreno/ui`/feature packages reference them only when their feature IPs land (this IP is binary readiness, not usage) |
|
|
33
|
-
| Stripe Android constraints | Pin Kotlin 2.x / `compileSdkVersion`
|
|
33
|
+
| Stripe Android constraints | Pin Kotlin 2.x / `compileSdkVersion` via `expo-build-properties` per stripe-react-native requirements. Implemented as Kotlin `2.2.21` (Stripe's default, in Expo's KSP table) and `compileSdkVersion` `37`: React Native 0.88 already compiles against 37, so pinning 36 would downgrade it. Stripe needs at least 36 |
|
|
34
34
|
| Expo Go story | Document which flows degrade in Expo Go (purchases → RevenueCat Preview API mode; stripe/local-auth → dev build required); CI smoke keeps web unaffected |
|
|
35
35
|
| Release process | Follows the `release` skill + the upgrade-notes-for-breaking-releases CI gate (PR #987): major tag, upgrade note required |
|
|
36
36
|
|
|
37
|
+
## Implementation notes (Terreno 58)
|
|
38
|
+
|
|
39
|
+
Versions, chosen against Expo SDK 58 (`bundledNativeModules.json` from `expo@58.0.14`):
|
|
40
|
+
|
|
41
|
+
| Package | Pin | Source |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `@stripe/stripe-react-native` | `0.81.0` | Expo 58 bundled version |
|
|
44
|
+
| `react-native-purchases`, `react-native-purchases-ui` | `10.12.2` | Newest release past the 7-day publish cooldown; peers `react-native >= 0.73` |
|
|
45
|
+
| `expo-device` | `~58.0.5` | Expo 58 bundled version |
|
|
46
|
+
| `expo-crypto` | `~58.0.2` | Existing catalog pin, unchanged |
|
|
47
|
+
| `expo-local-authentication` | `~58.0.2` | Expo 58 bundled version |
|
|
48
|
+
| `expo-system-ui` | `~58.0.6` | Expo 58 bundled version |
|
|
49
|
+
| `expo-build-properties` | `~58.0.11` | Expo 58 bundled version |
|
|
50
|
+
|
|
51
|
+
**Dropped: `react-native-otp-verify`.** `expo-doctor` reports it as unsupported on the New
|
|
52
|
+
Architecture (React Native Directory metadata), and React Native 0.88 runs only the New
|
|
53
|
+
Architecture. The latest release (1.2.0) is a legacy bridge module with no iOS sources. One-time
|
|
54
|
+
codes use `TextInput` `textContentType="oneTimeCode"` (iOS) and `autoComplete="sms-otp"`
|
|
55
|
+
(Android), which need no native module. Android SMS Retriever auto-read needs a New
|
|
56
|
+
Architecture module in a later binary if a feature requires it.
|
|
57
|
+
|
|
58
|
+
`expo-local-authentication` also gets its config plugin (`faceIDPermission`) so Face ID works
|
|
59
|
+
on iOS without another binary.
|
|
60
|
+
|
|
37
61
|
## Architecture
|
|
38
62
|
|
|
39
63
|
Changes are dependency + config only:
|
|
@@ -94,8 +118,8 @@ See [docs/tasks/native-module-baseline.md](../tasks/native-module-baseline.md).
|
|
|
94
118
|
|
|
95
119
|
## Acceptance Criteria
|
|
96
120
|
|
|
97
|
-
- [
|
|
98
|
-
seven packages added.
|
|
121
|
+
- [x] `bun install`, full compile, and `example-frontend` web export succeed with the
|
|
122
|
+
seven packages added (`react-native-otp-verify` dropped; see Implementation notes).
|
|
99
123
|
- [ ] iOS and Android EAS dev builds compile; the diagnostics story shows all seven
|
|
100
124
|
modules resolved on device.
|
|
101
125
|
- [ ] Android build carries the Kotlin 2.x / compileSdk 36 pins without breaking
|