@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
|
@@ -96,7 +96,8 @@ src/
|
|
|
96
96
|
`getMCPTools`, `runBufferedChatTurn` (one chat turn to completion without HTTP: the same system
|
|
97
97
|
prompt, tools, and block checks as `/gpt/histories/:id/turn`; takes `{body: {historyId, prompt},
|
|
98
98
|
options, req}` where `req` carries `user` and `headers`)
|
|
99
|
-
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project` (lazy; prefer `getProjectModel()`)
|
|
99
|
+
- **Models:** `AIRequest`, `AIDataset`, `GptHistory`, `FileAttachment`, `Project` (lazy; prefer `getProjectModel()`)
|
|
100
|
+
- **Migrations:** `gptHistoryStringIdsMigration` (`{slug, up}` for an app migration file), `migrateGptHistoryStringIds`, `GPT_HISTORY_STRING_IDS_MIGRATION`, type `GptHistoryStringIdsResult` ([GptHistory String ids](#gpthistory-string-ids))
|
|
100
101
|
- **Routes:** `addGptRoutes`, `addGptHistoryRoutes`, `addAiRequestsExplorerRoutes`, `addFileRoutes`, `addProjectRoutes`, `addMcpRoutes`
|
|
101
102
|
- **Structured output:** `parseAiJson`, `normalizeLlmJsonTextForStructuredOutput`, re-exported `Output`, `jsonSchema`, `JSONValue`, `FlexibleSchema` from `ai`
|
|
102
103
|
- **Langfuse:** `initLangfuseClient`, `getLangfuseClient`, `shutdownLangfuseClient`, `compilePrompt`, `createPrompt`, `getPrompt`, `createTelemetryConfig`, `preparePromptForAI`, `initTracing`, `shutdownTracing`, `LangfuseCache`, cache helpers
|
|
@@ -224,10 +225,12 @@ Logs all AI calls for monitoring and admin explorer.
|
|
|
224
225
|
|
|
225
226
|
### GptHistory
|
|
226
227
|
|
|
227
|
-
Conversation history with multi-modal prompts.
|
|
228
|
+
Conversation history with multi-modal prompts. A synced collection: `/gpt/histories` streams each
|
|
229
|
+
user's histories to `@terreno/syncdb` clients (see [addGptHistoryRoutes](#addgpthistoryroutesrouter-options)).
|
|
228
230
|
|
|
229
231
|
| Field | Type | Description |
|
|
230
232
|
|-------|------|-------------|
|
|
233
|
+
| `_id` | string | A UUID for new histories, so offline sync clients can mint ids. Histories saved before 58 keep their old ObjectId as a hex string after the [migration](#gpthistory-string-ids) |
|
|
231
234
|
| `userId` | ObjectId | Owner (required) |
|
|
232
235
|
| `title` | string? | Auto-generated on the first chat turn's reply (`/gpt/prompt` or `turn`) when empty |
|
|
233
236
|
| `projectId` | ObjectId? | Optional project association |
|
|
@@ -238,6 +241,30 @@ Assistant replies from `/gpt/prompt` carry `streamId` and `status`: `streaming`
|
|
|
238
241
|
|
|
239
242
|
**Virtual:** `ownerId` aliases `userId` for `Permissions.IsOwner`.
|
|
240
243
|
|
|
244
|
+
**Plugins:** `createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`, `syncPlugin`. `syncPlugin` refuses `GptHistory.deleteMany`, `updateMany`, hard deletes, upserts, and query writes whose filter has no `_id`. Write one history at a time by `_id`; in tests, clear the collection with `GptHistory.collection.deleteMany({})`.
|
|
245
|
+
|
|
246
|
+
`AIDataset.historyId` is the history's String `_id`. `registerAiDataset` still accepts an ObjectId and stores its hex string.
|
|
247
|
+
|
|
248
|
+
#### GptHistory String ids
|
|
249
|
+
|
|
250
|
+
Terreno 58 changed `GptHistory._id` from ObjectId to String. Histories saved before the upgrade still have ObjectId `_id`s, which the String schema cannot find. The `gpt-history-string-ids` migration moves them. Add it to the app's `migrations/` directory and apply it with `terreno-migrate` before deploying 58 frontends:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// migrations/20261022130000-gpt-history-string-ids.ts
|
|
254
|
+
import {gptHistoryStringIdsMigration} from "@terreno/ai";
|
|
255
|
+
|
|
256
|
+
export const id = "20261022130000-gpt-history-string-ids";
|
|
257
|
+
export const up = gptHistoryStringIdsMigration.up;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Step | Effect |
|
|
261
|
+
|------|--------|
|
|
262
|
+
| 1 | Copy each ObjectId history (500 per batch) to `_id: oldId.toHexString()`, keeping every stored field. URLs and clients that hold the hex id keep working |
|
|
263
|
+
| 2 | Rewrite `AIDataset.historyId` refs from the ObjectId to the hex string, including refs to histories that no longer exist |
|
|
264
|
+
| 3 | Delete the ObjectId originals |
|
|
265
|
+
|
|
266
|
+
A dry run (`terreno-migrate up --dry`) only counts the histories and refs it would change. The migration writes through the MongoDB driver, so it does not stamp sync seqs; moved histories reach sync clients as unstamped legacy rows on the next snapshot. Running it again changes nothing, and a run stopped partway finishes on the next run. It has no `down`: new histories get UUIDs, which have no ObjectId to go back to. `migrateGptHistoryStringIds({connection, dryRun, batchSize?, logger?, checkCancellation?})` runs the same steps without a migration file and returns `{historiesMigrated, datasetsUpdated}`.
|
|
267
|
+
|
|
241
268
|
### FileAttachment
|
|
242
269
|
|
|
243
270
|
Metadata for files stored in GCS.
|
|
@@ -412,6 +439,8 @@ CRUD at `/gpt/histories` via `modelRouter`:
|
|
|
412
439
|
| Create, List | `IsAuthenticated` |
|
|
413
440
|
| Read, Update, Delete | `IsOwner` |
|
|
414
441
|
|
|
442
|
+
Registered with the three-argument `modelRouter("/gpt/histories", GptHistory, {sync: {scope: {type: "owner", field: "userId"}}})`, so `SyncApp` serves the histories as the sync collection `gpt/histories` on each user's stream `gpt/histories|owner:{userId}`. A client reads them with `useQuery("gpt/histories")` and renames or deletes them with `useMutate("gpt/histories")`; sync payloads go through the same response handler as REST, so they also leave out `pendingAsk.promptIndex` and `pendingAsk.responseMessages`. Register `SyncApp` (and `RealtimeApp` for live deltas) in the app. REST CRUD stays for clients that do not sync. Every history route takes the String `_id`.
|
|
443
|
+
|
|
415
444
|
Query filtered by `userId`; sort `-updated`; query fields `userId`, `projectId`. Create sets `userId` to the caller, so `{}` is a valid create body even when the app validates request bodies. Create and update bodies drop `pendingAsk` (including dotted `pendingAsk.*` paths), so only a chat turn writes it; the OpenAPI spec marks it `readOnly` on create and update. Create and update bodies also drop any `gcsKey` on `prompts[].content[]` parts (including dotted `prompts.N.content.M.gcsKey` paths) that the history does not already store, so a client can send stored prompts back but cannot add an attachment key.
|
|
416
445
|
|
|
417
446
|
Pass `chat`, the options given to `addGptRoutes`, to add two headless actions for clients that do not read server-sent events, such as a watch app. Both exist only when `chat` turns `asks` on: without `chat`, or with `asks` off, neither action exists, so a host that never turned asks on gets no new endpoints. `AiApp` adds them when its `asks` option is set.
|
|
@@ -434,6 +463,9 @@ Bodies, results, and errors: [Headless endpoints](agent-ui-asks.md#headless-endp
|
|
|
434
463
|
### addFileRoutes(router, options)
|
|
435
464
|
|
|
436
465
|
Requires `fileStorageService` and `gcsBucket` (registered by `AiApp` when both are set).
|
|
466
|
+
`fileStorageService` may also be a function that returns the current service or `undefined`.
|
|
467
|
+
Use it when storage is configured at runtime: the routes stay mounted and documented in
|
|
468
|
+
`/openapi.json`, and answer `503 File storage is not configured` while it returns `undefined`.
|
|
437
469
|
|
|
438
470
|
| Endpoint | Method | Auth | Description |
|
|
439
471
|
|----------|--------|------|-------------|
|
|
@@ -495,7 +527,7 @@ new AiApp({
|
|
|
495
527
|
| `toolChoice` | `"auto"` \| `"none"` \| `"required"` (default `"auto"` when tools present) |
|
|
496
528
|
| `maxSteps` | Max tool-calling steps (default 5) |
|
|
497
529
|
| `titleModelId` | Cheaper model for conversation title generation |
|
|
498
|
-
| `openApiOptions` | Passed to route OpenAPI builders |
|
|
530
|
+
| `openApiOptions` | Passed to route OpenAPI builders. Defaults to the OpenAPI instance `TerrenoApp` passes to `register` |
|
|
499
531
|
| `projects` | Default `true`. `false` skips the `/gpt/projects` routes, so `AiApp` never registers the `Project` model |
|
|
500
532
|
|
|
501
533
|
## Cost and abuse limits
|
|
@@ -670,7 +702,7 @@ When `prompts.primary` is `local`, `ObservabilityApp.register` mounts admin-only
|
|
|
670
702
|
| GET/POST | `/ai/observability/evaluators` | List / create. Create accepts `target: "full trace"` only (`generation span` and `dataset item` → 400). Seeded template install can still store other targets. `llm-judge` requires `judgePromptName`; create rejects when the judge prompt `outputSchema` omits a required dimension (400 names the key). `json-assert` supports `assertion` (`path` + `constraint`) or built-in output-schema mode. Human + `liveSampleRate > 0` → 400. Numeric dimension `range` is `min-max` (for example `0-1`); categorical `range` is `label|label` |
|
|
671
703
|
| GET/PATCH/DELETE | `/ai/observability/evaluators/:id` | Read / update / soft-delete |
|
|
672
704
|
| POST | `/ai/observability/traces/review` | Enqueue one or many traces against a human evaluator (`reason: "manual"`) |
|
|
673
|
-
| GET | `/ai/observability/review` | Queue by `status` with counts; oldest-first. Response includes `more: false` so
|
|
705
|
+
| GET | `/ai/observability/review` | Queue by `status` with counts; oldest-first. Response includes `more: false` so request clients keep the count envelope instead of unwrapping `data`. Rows include `traceName`, `promptName`, assignee, reason, and enqueue time |
|
|
674
706
|
| GET | `/ai/observability/review/:id` | Item + evaluator dimensions + `given` / `wrote` panels and `rawInput` / `rawOutput` for the Raw JSON disclosure |
|
|
675
707
|
| POST | `/ai/observability/review/:id` | `submit` (scores via ScoreSinks, status `done`), `skip`, or `assign` |
|
|
676
708
|
|
|
@@ -82,7 +82,7 @@ The plugin applies `matchAudienceByType` before `matchAudience`. When `isStaff`
|
|
|
82
82
|
| Method | Path | Description |
|
|
83
83
|
|--------|------|-------------|
|
|
84
84
|
| GET | `/announcements/pending` | Current interrupt (`modal` or `banner` only) + `remainingCount` (`platform` query: `ios` \| `android` \| `web`; optional `version` build number) |
|
|
85
|
-
| GET | `/announcements/feed` | Paginated published changelog — all display modes (`platform` and optional `version` as above) |
|
|
85
|
+
| GET | `/announcements/feed` | Paginated published changelog — all display modes (`platform` and optional `version` as above; `limit` 1–100, default 20; `page` from 1). Returns `{data, more, page, total}`; `more` is true when a later page has items. |
|
|
86
86
|
| POST | `/announcements/:id/acknowledge` | Record acknowledgement (idempotent per version). Same visibility as pending/feed; **404** when not visible. |
|
|
87
87
|
| POST | `/announcements/:id/impression` | Record a view (`{ platform? }`). Same visibility as pending/feed; **404** when not visible. **400** for unknown keys or an invalid `platform`. |
|
|
88
88
|
| POST | `/announcements/:id/click` | Record a primary-action click (`{ action: "primaryAction", platform? }`; optional `?version=` for min-build visibility). Returns **404** when not visible (checked before action/CTA validation). **400** when `action` is invalid, `primaryAction` is absent, or an explicit `platform` value is invalid. |
|
|
@@ -172,11 +172,11 @@ Set these environment variables on the MCP server to include live announcements:
|
|
|
172
172
|
|
|
173
173
|
### `useAcknowledgeAnnouncement`
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Sends acknowledgement, impression, and primary-action click events through the `client` passed to `useAcknowledgeAnnouncement(client, baseUrl?)` (see [`TerrenoRequestClient`](ui.md#terrenorequestclient)):
|
|
176
176
|
|
|
177
177
|
| Method | Route | Notes |
|
|
178
178
|
|--------|-------|-------|
|
|
179
|
-
| `acknowledge(id)` | `POST /announcements/:id/acknowledge` |
|
|
179
|
+
| `acknowledge(id)` | `POST /announcements/:id/acknowledge` | Refetches the pending announcement in every mounted `useAnnouncements` on the same client. Query includes `platform` and, when available, `version` (same visibility gate as pending/feed). |
|
|
180
180
|
| `recordImpression(id)` | `POST /announcements/:id/impression` | Sends current `platform` in body |
|
|
181
181
|
| `recordClick(id)` | `POST /announcements/:id/click` | Body `{ action: "primaryAction", platform }`; query includes `platform` and, when available, `version` (same visibility gate as pending/feed) |
|
|
182
182
|
|
|
@@ -49,11 +49,16 @@ REST API framework built on Express and Mongoose. Provides modelRouter (CRUD end
|
|
|
49
49
|
|
|
50
50
|
Two patterns for building Terreno APIs:
|
|
51
51
|
|
|
52
|
-
###
|
|
52
|
+
### Removed: modelRouter `realtime`
|
|
53
53
|
|
|
54
|
-
`modelRouter({ realtime })`
|
|
54
|
+
Terreno 58 removed `modelRouter({ realtime })` and the legacy RTK `sync` websocket event. Passing `realtime` is now a TypeScript error. Use `sync` with [`@terreno/syncdb`](syncdb.md). See [Upgrade from @terreno/rtk to @terreno/syncdb](../how-to/migrate-rtk-to-syncdb.md).
|
|
55
55
|
|
|
56
|
-
`
|
|
56
|
+
Also removed: `RealtimeConfig`, `RealtimeEvent`, `DocumentSubscription`, `QuerySubscription`, the realtime registry (`registerRealtime`, `getRealtimeRegistry`, `findRegistryEntryByCollection`, `findRegistryEntryByRoutePath`, `updateRealtimeRegistryOptions`, `clearRealtimeRegistry`, `RealtimeRegistryEntry`), the query-subscription store (`addQuerySubscription`, `removeQuerySubscription`, `removeAllSocketQueries`, `getQuerySubscriptionsForCollection`, `computeQueryId`, `clearQueryStore`), and `MAX_MODEL_SUBSCRIPTIONS` / `MAX_DOCUMENT_SUBSCRIPTIONS` / `MAX_QUERY_SUBSCRIPTIONS`. The server no longer handles `subscribe:model`, `subscribe:document`, or `subscribe:query`.
|
|
57
|
+
|
|
58
|
+
`RealtimeApp` stays. It hosts Socket.io, socket auth, change streams, and `sync:delta`.
|
|
59
|
+
Before it sends a `sync:delta` to a socket, it runs the router's `read` permissions (or RBAC
|
|
60
|
+
read access) against the change's post-image hydrated as a model document, as REST does. So a
|
|
61
|
+
virtual owner field works: `GptHistory`'s `ownerId` alias of `userId` passes `Permissions.IsOwner`.
|
|
57
62
|
|
|
58
63
|
Keep `admin.realtime` when you want admin `admin:model.changed` events.
|
|
59
64
|
|
|
@@ -99,15 +104,21 @@ new TerrenoApp({
|
|
|
99
104
|
|
|
100
105
|
### HTTP rate limiting
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
On by default. Omitted, `undefined`, or `{}` mounts the limiter with the memory store and the defaults below. `rateLimit: false` turns it off. On Cloud Run pass `trustProxy: 1`.
|
|
103
108
|
|
|
104
109
|
```typescript
|
|
105
110
|
new TerrenoApp({
|
|
106
111
|
userModel: User,
|
|
107
|
-
rateLimit:
|
|
112
|
+
rateLimit: {trustProxy: 1}, // or `false` to turn the limiter off
|
|
108
113
|
});
|
|
109
114
|
```
|
|
110
115
|
|
|
116
|
+
| `rateLimit` | Result |
|
|
117
|
+
|-------------|--------|
|
|
118
|
+
| omitted / `undefined` | On: memory store, default limits |
|
|
119
|
+
| `{}` / `{store, limits, trustProxy, skip, betterAuthBasePath}` | On with those options |
|
|
120
|
+
| `false` | Off |
|
|
121
|
+
|
|
111
122
|
| Option | Default | Meaning |
|
|
112
123
|
|--------|---------|---------|
|
|
113
124
|
| `store` | `"memory"` | `"memory"` \| `"redis"` \| `"mongo"` |
|
|
@@ -211,7 +222,7 @@ Both patterns create the same middleware stack (CORS, auth, logging, OpenAPI). H
|
|
|
211
222
|
|
|
212
223
|
## Collection catalog
|
|
213
224
|
|
|
214
|
-
`modelRouter("/path", Model, options)` writes one catalog record per route path. MCP
|
|
225
|
+
`modelRouter("/path", Model, options)` writes one catalog record per route path. MCP and sync surfaces read that record; TerrenoApp calls `replaceCollectionOptions` once when access control is injected. Test helpers `clearMCPRegistry` and `clearSyncRegistry` clear the entire catalog.
|
|
215
226
|
|
|
216
227
|
## modelRouter actions
|
|
217
228
|
|
|
@@ -382,7 +393,7 @@ Create, update, and delete tools share REST permission, hook, and persistence se
|
|
|
382
393
|
|
|
383
394
|
### MCP service token model
|
|
384
395
|
|
|
385
|
-
`McpServiceToken` stores the hashed credential records used by the optional MCP service-token feature.
|
|
396
|
+
`McpServiceToken` stores the hashed credential records used by the optional MCP service-token feature. `_id` is a String (UUID for new rows; ObjectId hex for rows migrated from 57.x). The schema uses `isDeletedPlugin` and `syncPlugin`, so with `mcpServiceTokens` enabled the collection is synced read-only to its owner.
|
|
386
397
|
|
|
387
398
|
```typescript
|
|
388
399
|
import {McpServiceToken} from "@terreno/api";
|
|
@@ -394,17 +405,23 @@ const {mcpServiceToken, token} = await McpServiceToken.issueFor(
|
|
|
394
405
|
// Store or show `token` once. Only its SHA-256 hash is persisted.
|
|
395
406
|
```
|
|
396
407
|
|
|
397
|
-
The token begins with `mcp_` followed by 32 random bytes encoded as hex. `issueFor` records the user's `tokenEpoch` (pass it on the user argument; default 0). `/mcp` refuses a token whose recorded epoch differs from the user's current `tokenEpoch` and sets its `revokedAt`, so a password reset, Better Auth password change, or session revocation ends every key minted before it. A row stored without `tokenEpoch` counts as epoch 0. `revokeOutdatedForUser(user)` revokes the user's keys from earlier epochs; the list and create routes call it so the list shows them as revoked. `verify(token)` returns `null` for unknown, expired, or revoked tokens. `revokeForUser(user, tokenId)` records `revokedAt`, and `countActiveForUser(userId)` excludes expired or revoked records.
|
|
408
|
+
The token begins with `mcp_` followed by 32 random bytes encoded as hex. `issueFor` records the user's `tokenEpoch` (pass it on the user argument; default 0). `/mcp` refuses a token whose recorded epoch differs from the user's current `tokenEpoch` and sets its `revokedAt`, so a password reset, Better Auth password change, or session revocation ends every key minted before it. A row stored without `tokenEpoch` counts as epoch 0. `revokeOutdatedForUser(user)` revokes the user's keys from earlier epochs; the list and create routes call it so the list shows them as revoked. `verify(token)` returns `null` for unknown, expired, or revoked tokens. `revokeForUser(user, tokenId)` records `revokedAt`, and `countActiveForUser(userId)` excludes expired or revoked records. A delete never removes or tombstones the row: document `deleteOne` and a soft delete (admin DELETE sets `deleted: true`) both set `revokedAt` and keep `deleted: false`, so the owner's synced row shows the revoke. `revokeAllForUser(userId)` revokes every active token the user holds and returns the count. Never return or log `tokenHash` or the plaintext token outside the initial issue result.
|
|
398
409
|
|
|
399
|
-
Self-serve
|
|
410
|
+
Self-serve routes are `modelRouter("/mcp/service-tokens", McpServiceToken, {sync: {scope: {type: "owner", field: "userId"}}})`. `TerrenoApp` mounts them when `mcpServiceTokens` is enabled, passing its OpenAPI bundle. You can also call `addMcpServiceTokenRoutes(app, {publicMcpUrl, openApi})` yourself, or build the registration with `createMcpServiceTokenRouter(options)`. They require session or JWT auth and **reject** `Authorization: Bearer mcp_…` so a service token cannot mint, list, or revoke tokens.
|
|
400
411
|
|
|
401
412
|
| Method | Path | Body / params | Response |
|
|
402
413
|
| --- | --- | --- | --- |
|
|
403
|
-
| POST | `/mcp/service-tokens` | `{name, expiresAt?}` | `{data: {id, name, token, tokenPrefix, mcpUrl, expiresAt, created}}` |
|
|
404
|
-
| GET | `/mcp/service-tokens` | `?page&limit` | `{data, page, limit,
|
|
405
|
-
|
|
|
414
|
+
| POST | `/mcp/service-tokens/create` | `{name, expiresAt?}` | `{data: {id, name, token, tokenPrefix, mcpUrl, expiresAt, created}}` (operation `mcpServiceTokens_create`) |
|
|
415
|
+
| GET | `/mcp/service-tokens` | `?page&limit` | `{data, page, limit, more, total}` — the caller's rows, no `token` or `tokenHash` |
|
|
416
|
+
| GET | `/mcp/service-tokens/:id` | — | `{data}` — owner only (`403` otherwise) |
|
|
417
|
+
| POST | `/mcp/service-tokens/:id/revoke` | — | `{data}` — the revoked row without `tokenHash` (operation `mcpServiceTokens_revoke`) |
|
|
418
|
+
| POST / PATCH / DELETE | `/mcp/service-tokens[/:id]` | — | `405`: client writes are disabled |
|
|
419
|
+
|
|
420
|
+
`expiresAt` is an optional ISO-8601 datetime (Luxon `DateTime.fromISO`). Omit it for a token that does not expire. Create returns `mcpUrl` from `publicMcpUrl`, else `BETTER_AUTH_URL`, else the request host, with `/mcp` appended when missing. An 11th **active** token for the same user is `400`. Revoking an unknown, foreign, or already revoked token is `404`. List includes revoked rows for the owner so the UI can show history. Default and maximum page size is 100.
|
|
421
|
+
|
|
422
|
+
Sync: collection `mcp/service-tokens`, stream `mcp/service-tokens|owner:{userId}`. Snapshot entities, `/sync/entities` rows, and `sync:delta` payloads go through `toPublicMcpServiceToken`, which drops `tokenHash`. Sync `create`, `update`, and `delete` mutations are nacked `unauthorized`; mint and revoke through the actions above.
|
|
406
423
|
|
|
407
|
-
`
|
|
424
|
+
Migration: `mcpServiceTokenStringIdsMigration.up` (slug `mcp-service-token-string-ids`) re-inserts each ObjectId row with `_id = oldId.toHexString()` and deletes the original. It is idempotent, resumes an interrupted run, and works without transactions. `migrateMcpServiceTokenStringIds(ctx)` returns `{converted, pending}`. Wire it into an app `migrations/` file as shown in [Run MongoDB migrations](../how-to/run-mongodb-migrations.md#framework-migrations).
|
|
408
425
|
|
|
409
426
|
### MCP OAuth
|
|
410
427
|
|
|
@@ -489,8 +506,7 @@ setupServer({
|
|
|
489
506
|
- `POST /auth/refresh_token` — Refresh access token
|
|
490
507
|
- `POST /auth/forgotPassword` — Always 202; mails a reset link only when the email exists (with
|
|
491
508
|
`BetterAuthApp` registered, only when that user already has a JWT password)
|
|
492
|
-
- `POST /auth/resetPassword` — `{token, password}`; also aliased at `POST /resetPassword` for
|
|
493
|
-
RTK client. With `BetterAuthApp` registered, a token for a user without a JWT password gets 400
|
|
509
|
+
- `POST /auth/resetPassword` — `{token, password}`; also aliased at `POST /resetPassword` (the `jwtAuthAdapter` `resetPassword` path in `@terreno/syncdb/auth`). With `BetterAuthApp` registered, a token for a user without a JWT password gets 400
|
|
494
510
|
`legacy-password-reset-unavailable` and sets no password (`POST /auth/login` has no second
|
|
495
511
|
factor); that user resets through Better Auth
|
|
496
512
|
- `POST /auth/sendVerification` — Authenticated; 202 only after delivery succeeds; mails a verification link when `emailVerified` is not true
|
|
@@ -515,8 +531,8 @@ letter case, needs no password. `currentPassword` is never saved.
|
|
|
515
531
|
| User has no password (for example GitHub-only or social-only) | 400 `Set a password on your account before changing your email. …` | Nothing |
|
|
516
532
|
| Right `currentPassword` | 200 with the updated user | The whole body |
|
|
517
533
|
|
|
518
|
-
The status is 403, not 401, so
|
|
519
|
-
session and log the user out.
|
|
534
|
+
The status is 403, not 401, so the syncdb request client does not treat a wrong password as an
|
|
535
|
+
expired session and log the user out.
|
|
520
536
|
|
|
521
537
|
The password is checked against the passport-local-mongoose hash first. A wrong password
|
|
522
538
|
counts toward the model's passport lockout when `limitAttempts` is on, the same counter
|
|
@@ -593,7 +609,7 @@ case instead of 202. Email lookup is case-insensitive. Successful reset calls `s
|
|
|
593
609
|
and deletes Better Auth sessions. Better Auth password reset updates the JWT password of the
|
|
594
610
|
matching app User when it already has one, and bumps its `tokenEpoch`. Access
|
|
595
611
|
tokens stay valid until expiry (default 15m). `POST /resetPassword`
|
|
596
|
-
matches the `@terreno/
|
|
612
|
+
matches the `@terreno/syncdb/auth` `jwtAuthAdapter` `resetPassword` path (`password` or `newPassword`).
|
|
597
613
|
|
|
598
614
|
For any other server-side password change, such as an admin "set password" action, call
|
|
599
615
|
`changeUserPassword(user, password, {adminId, app: req.app})`. It does what reset does: sets the
|
|
@@ -821,7 +837,7 @@ Endpoints it adds, under `basePath` (default `/api/auth`):
|
|
|
821
837
|
cookie, and redirects to the provider. A link that cannot start redirects to
|
|
822
838
|
`errorCallbackURL` (or `callbackURL`) with `error=session_expired`, `error=provider_not_found`,
|
|
823
839
|
or `error=link_failed`. The provider callback links the account and redirects to `callbackURL`
|
|
824
|
-
with no `#ott`, since no session is created.
|
|
840
|
+
with no `#ott`, since no session is created. `linkSocialWithHandoff` from `@terreno/syncdb/auth` drives both steps;
|
|
825
841
|
see [Linking a provider in bearer mode](../how-to/configure-better-auth.md#linking-a-provider-in-bearer-mode).
|
|
826
842
|
|
|
827
843
|
With the opt-in `rateLimit`, every path above is in the auth bucket. Better Auth's own limiter
|
|
@@ -832,7 +848,7 @@ allows three starts, three link intents, and three link starts per ten seconds p
|
|
|
832
848
|
Every Better Auth server answers `GET {basePath}/terreno/methods` (default
|
|
833
849
|
`/api/auth/terreno/methods`) with the sign-in methods it has enabled, so a login screen renders
|
|
834
850
|
only those. It needs no session. Read it from a frontend with `useAuthMethods(authClient)` from
|
|
835
|
-
`@terreno/
|
|
851
|
+
`@terreno/syncdb/auth`.
|
|
836
852
|
|
|
837
853
|
``````json
|
|
838
854
|
{
|
|
@@ -870,8 +886,7 @@ plugin-registered providers are reflected.
|
|
|
870
886
|
|
|
871
887
|
Every Better Auth server serves `POST {basePath}/terreno/set-password` (default
|
|
872
888
|
`/api/auth/terreno/set-password`) so a signed-in user who has no password, such as one who signed
|
|
873
|
-
up with a provider, can add one. Better Auth's own `setPassword` is server-only.
|
|
874
|
-
`useAccountSecurity` calls it from the password card's "Set a password" form.
|
|
889
|
+
up with a provider, can add one. Better Auth's own `setPassword` is server-only. `useAccountSecurity` from `@terreno/syncdb/auth` calls it from the password card's "Set a password" form.
|
|
875
890
|
|
|
876
891
|
| Request | Response |
|
|
877
892
|
| --- | --- |
|
|
@@ -903,8 +918,8 @@ means the cookie expired. Only values the response sets are carried. Such a sign
|
|
|
903
918
|
answers with a two-factor challenge has no `set-auth-token`. The header is listed in
|
|
904
919
|
`Access-Control-Expose-Headers`. On `{basePath}/two-factor/*`, `/passkey/*`, and `/sign-in/*`,
|
|
905
920
|
the header's challenge cookies are merged into the request's cookies unless a real cookie of
|
|
906
|
-
the same name is present; other names, including session cookies, are ignored.
|
|
907
|
-
uses it; see
|
|
921
|
+
the same name is present; other names, including session cookies, are ignored. The
|
|
922
|
+
`@terreno/syncdb/auth` bearer mode uses it; see
|
|
908
923
|
[Challenges without cookies](../explanation/authentication.md#challenges-without-cookies).
|
|
909
924
|
|
|
910
925
|
**Endpoints (when enabled):**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Install: `bun add -g @terreno/cli` then run `terreno --help`.
|
|
4
4
|
|
|
5
|
-
The `terreno` binary is the operator CLI for Terreno apps. It wraps documentation search, codegen (syncdb SDK,
|
|
5
|
+
The `terreno` binary is the operator CLI for Terreno apps. It wraps documentation search, codegen (syncdb collection SDK, typed API request SDK, Mongoose models, routes, screens, forms, admin install notes), app bootstrap, local project tools from `@terreno/mcp`, and **OpenAPI-driven REST calls**.
|
|
6
6
|
|
|
7
7
|
Generate an app-specific CLI from your backend spec:
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ terreno generate rest-cli --schema http://localhost:4000/openapi.json --out ./to
|
|
|
18
18
|
| `docs component <Name>` | Print `@terreno/ui` component props |
|
|
19
19
|
| `docs upgrade --from --to` | Lockstep upgrade notes |
|
|
20
20
|
| `generate syncdb` | Typed syncdb hooks from OpenAPI (`--schema`, `--out`, `--collections`, `--config`, `--no-format`) |
|
|
21
|
-
| `generate sdk` |
|
|
21
|
+
| `generate sdk` | Typed request hooks (`apiSdk.ts`) for every non-synced route via the syncdb codegen (`--schema`, `--out`, `--config`, `--no-format`); same output as `terreno-syncdb-codegen --api-out`. A legacy RTK `--config openapi-config.ts` alone is rejected |
|
|
22
22
|
| `generate rest-cli` | Scaffold a bun CLI from OpenAPI (`--schema`, `--out`, `--name`, `--base-url`) |
|
|
23
23
|
| `generate model` | Mongoose model (`--name`, `--field name:Type:required`, `--owner`, `--soft-delete`) |
|
|
24
24
|
| `generate route` | `modelRouter` module (`--model-name`, `--route-path`, `--owner-filtered`, `--query-fields`, `--sort`) |
|
|
@@ -33,8 +33,7 @@ terreno generate rest-cli --schema http://localhost:4000/openapi.json --out ./to
|
|
|
33
33
|
| `api request <METHOD> <path>` | Invoke by method + path template |
|
|
34
34
|
| `info` | Local `@terreno/*` versions (via MCP local tools) |
|
|
35
35
|
| `logs` | Merge backend/browser JSONL, Metro events, and app CDP console (`--entries`, `--level`, `--since`, `--sources`; `last-error`) |
|
|
36
|
-
| `state` | Inspect
|
|
37
|
-
| `eval` | Evaluate JavaScript in the app runtime through Metro CDP (`--code`; requires `TERRENO_MCP_EVAL=1`) |
|
|
36
|
+
| `state` | Inspect the live SyncDB client (`get_syncdb_state`): status, entities, outbox, conflicts, cursors, streams, debug events (`--collection`, `--entity-id`, `--limit`, `--name`, `--no-events`). Needs `createSyncDb({debug: true})`. The Terreno 57 `--slice` / `--query` Redux flags were removed in 58 and now exit 1 || `eval` | Evaluate JavaScript in the app runtime through Metro CDP (`--code`; requires `TERRENO_MCP_EVAL=1`) |
|
|
38
37
|
| `navigate` | Navigate Expo Router through Metro CDP (requires `TERRENO_MCP_EVAL=1`) |
|
|
39
38
|
| `web [url]` | Automate the web app with Bun 1.4 `Bun.WebView`; ordered `--action` JSON, `--snapshot`, and `--screenshot` |
|
|
40
39
|
| `db schema` | Mongo collections (`--collection-filter`, `--summary`) |
|
|
@@ -85,10 +85,10 @@ Comprehensive guide to environment variables used across Terreno packages and ex
|
|
|
85
85
|
|
|
86
86
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
87
87
|
|----------|---------|----------|---------|--------|-------|
|
|
88
|
-
| `EXPO_PUBLIC_API_URL` | `@terreno/
|
|
89
|
-
| `EXPO_PUBLIC_DEV_API_PORT` | `@terreno/
|
|
90
|
-
| `EXPO_PUBLIC_BUILD_NUMBER` |
|
|
91
|
-
| `OPENAPI_URL` | example-frontend `sdk`
|
|
88
|
+
| `EXPO_PUBLIC_API_URL` | `@terreno/syncdb/config` | ✅ prod web | dev auto-detect | No | **client** |
|
|
89
|
+
| `EXPO_PUBLIC_DEV_API_PORT` | `@terreno/syncdb/config` | ❌ | `4000` | No | **client** |
|
|
90
|
+
| `EXPO_PUBLIC_BUILD_NUMBER` | `@terreno/syncdb/buildNumber` (`app.config.ts`) | ❌ | git commit count | No | **client** |
|
|
91
|
+
| `OPENAPI_URL` | example-frontend `sdk` / `sync-sdk` scripts | ❌ | `http://localhost:4000/openapi.json` | No | tooling |
|
|
92
92
|
|
|
93
93
|
### `app.json` `extra` (not `process.env`)
|
|
94
94
|
|
|
@@ -96,18 +96,17 @@ Configured in `expo.extra` and read via `expo-constants`:
|
|
|
96
96
|
|
|
97
97
|
| Key | Read by | Notes |
|
|
98
98
|
|-----|---------|-------|
|
|
99
|
-
| `BASE_URL` | `@terreno/
|
|
100
|
-
| `DEV_API_PORT` | `@terreno/
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `SOCKET_TRANSPORTS` | example-frontend (`store/socketTransports.ts`) | Comma-separated Socket.IO transports passed to `useSocketConnection({transports})` and `createSyncDb({socketTransports})`. Unset keeps `polling,websocket`; the Vercel build sets `websocket` |
|
|
104
|
-
| `APP_ENV` | `@terreno/rtk` | Logged at startup |
|
|
99
|
+
| `BASE_URL` | `@terreno/syncdb/config` | Production fallback when `EXPO_PUBLIC_API_URL` unset; use `__SAME_ORIGIN__` for same-origin admin SPA |
|
|
100
|
+
| `DEV_API_PORT` | `@terreno/syncdb/config` | Override dev API port (mirrors `EXPO_PUBLIC_DEV_API_PORT`) |
|
|
101
|
+
| `buildNumber` | `@terreno/syncdb/app` `useUpgradeCheck` | Sent to `GET /version-check`; set it with `resolveBuildNumber` |
|
|
102
|
+
| `SOCKET_TRANSPORTS` | example-frontend (`store/socketTransports.ts`) | Comma-separated Socket.IO transports passed to `createSyncDb({socketTransports})`. Unset keeps `polling,websocket`; the Vercel build sets `websocket` |
|
|
105
103
|
|
|
106
|
-
Resolution order for API base URL (`
|
|
104
|
+
Resolution order for API base URL (`syncdb/src/config/resolveBaseUrls.ts`):
|
|
107
105
|
|
|
108
|
-
1. `
|
|
109
|
-
2. `
|
|
110
|
-
3.
|
|
106
|
+
1. `extra.BASE_URL` = `__SAME_ORIGIN__` → the page origin (web)
|
|
107
|
+
2. `EXPO_PUBLIC_API_URL` (production and dev)
|
|
108
|
+
3. `extra.BASE_URL` (production only, when env unset)
|
|
109
|
+
4. Dev fallbacks: `hostUri`, experience URL, `localhost` on the dev API port
|
|
111
110
|
|
|
112
111
|
`EXPO_PUBLIC_API_URL` is inlined only into app source. Code under `node_modules`, including `@terreno/rtk`, does not see the inlined value in a production export and falls back to `extra.BASE_URL`, so set both for production web builds. `example-frontend/scripts/vercelBuild.ts` writes `extra.BASE_URL` from `EXPO_PUBLIC_API_URL` before `expo export`.
|
|
113
112
|
|
|
@@ -232,7 +231,7 @@ for authenticated test sends.
|
|
|
232
231
|
| `VALKEY_URL` | `@terreno/api` realtime adapter **and** `rateLimit.store: "redis"` | ❌ | — | Yes | server |
|
|
233
232
|
| `REDIS_URL` | `@terreno/api` fallback after `VALKEY_URL` (realtime + Redis rate-limit store) | ❌ | — | Yes | server |
|
|
234
233
|
|
|
235
|
-
There is **no** `RATE_LIMIT_ENABLED` (or similar) read by `@terreno/api`. Apps that want an env toggle pass `rateLimit: process.env.
|
|
234
|
+
There is **no** `RATE_LIMIT_ENABLED` (or similar) read by `@terreno/api`. The limiter is on by default. Apps that want an env toggle pass `rateLimit: process.env.RATE_LIMIT_DISABLED === "true" ? false : {trustProxy: 1}` themselves. See [Rate limiting](../how-to/rate-limiting.md).
|
|
236
235
|
|
|
237
236
|
## Webhooks & notifications
|
|
238
237
|
|
|
@@ -317,7 +316,8 @@ example-backend **test** setup only. They are not read by `@terreno/jobs`. Use
|
|
|
317
316
|
|
|
318
317
|
`SEED_DEFAULTS=true` runs the example seed (default users, all with the published password
|
|
319
318
|
`testpassword123`) when the server starts. A process with `NODE_ENV=production` logs a warning and
|
|
320
|
-
skips it, so no deployed revision
|
|
319
|
+
skips it, so no deployed revision holds those accounts. The exception is a PR preview, whose
|
|
320
|
+
`MONGO_DB_NAME` starts with `terreno-example-pr-`: it seeds its own throwaway database.
|
|
321
321
|
|
|
322
322
|
## Testing & CI (tooling only)
|
|
323
323
|
|
|
@@ -41,14 +41,16 @@ new TerrenoApp({userModel: User})
|
|
|
41
41
|
|
|
42
42
|
### Frontend
|
|
43
43
|
|
|
44
|
-
Use
|
|
44
|
+
Use `useFeatureFlags` from `@terreno/syncdb/flags` to read flags for the current user. It
|
|
45
|
+
loads `GET {basePath}/flagConfiguration` with the syncdb request client (the one from
|
|
46
|
+
`ApiClientProvider`) and refetches when the server pushes `featureFlagsChanged` on the
|
|
47
|
+
syncdb socket:
|
|
45
48
|
|
|
46
49
|
```typescript
|
|
47
|
-
import {useFeatureFlags} from "@terreno/
|
|
48
|
-
import {terrenoApi} from "@/store/sdk";
|
|
50
|
+
import {useFeatureFlags} from "@terreno/syncdb/flags";
|
|
49
51
|
|
|
50
|
-
const MyComponent = () => {
|
|
51
|
-
const {getFlag, getVariant, isLoading} = useFeatureFlags(
|
|
52
|
+
const MyComponent: React.FC<{userId?: string}> = ({userId}) => {
|
|
53
|
+
const {getFlag, getVariant, isLoading} = useFeatureFlags({skip: !userId, userId});
|
|
52
54
|
|
|
53
55
|
if (isLoading) return <Spinner />;
|
|
54
56
|
|
|
@@ -59,11 +61,41 @@ const MyComponent = () => {
|
|
|
59
61
|
};
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
New code can also use OpenFeature React hooks (`useBooleanFlagValue`, `useStringFlagValue`, `FeatureFlag`, …) after wiring `useTerrenoFeatureFlags` and `<OpenFeatureProvider domain="feature-flags">` — see
|
|
64
|
+
New code can also use OpenFeature React hooks (`useBooleanFlagValue`, `useStringFlagValue`, `FeatureFlag`, …) after wiring `useTerrenoFeatureFlags` and `<OpenFeatureProvider domain="feature-flags">` — see [how-to: add feature flags](../how-to/add-feature-flags.md).
|
|
65
|
+
|
|
66
|
+
#### `@terreno/syncdb/flags`
|
|
67
|
+
|
|
68
|
+
| Export | Description |
|
|
69
|
+
|--------|-------------|
|
|
70
|
+
| `useTerrenoFeatureFlags(options?)` | Loads `/flagConfiguration` into OpenFeature's `TypedInMemoryProvider` for `domain`; returns `{client, flags, isLoading, error, refetch}` |
|
|
71
|
+
| `useFeatureFlags(options?)` | Flat accessors over `useTerrenoFeatureFlags`: `{flags, getFlag, getVariant, isLoading, error, refetch}` |
|
|
72
|
+
| `TerrenoFlagConfiguration`, `TerrenoFlagDefinition` | Wire shape of `/flagConfiguration` |
|
|
73
|
+
| `DEFAULT_FEATURE_FLAGS_BASE_PATH`, `DEFAULT_FEATURE_FLAGS_DOMAIN`, `DEFAULT_FEATURE_FLAGS_EVENT` | `"/feature-flags"`, `"feature-flags"`, `"featureFlagsChanged"` |
|
|
74
|
+
|
|
75
|
+
Options (both hooks):
|
|
76
|
+
|
|
77
|
+
| Option | Default | Description |
|
|
78
|
+
|--------|---------|-------------|
|
|
79
|
+
| `userId` | `null` | Current user. A change refetches and sets the OpenFeature `targetingKey` |
|
|
80
|
+
| `skip` | `false` | Do not fetch or listen (for example while signed out) |
|
|
81
|
+
| `client` | `ApiClientProvider` client | Request client from `@terreno/syncdb/api` |
|
|
82
|
+
| `socket` | `SyncDbProvider` client | Anything with `onSocketEvent` (a `SyncDb`). Pass your `syncDb` when the hook sits above `SyncDbProvider`; `null` turns live refresh off |
|
|
83
|
+
| `socketEventName` | `"featureFlagsChanged"` | Push event that triggers a refetch |
|
|
84
|
+
| `basePath` | `"/feature-flags"` | `FeatureFlagsApp` `basePath` |
|
|
85
|
+
| `domain` | `"feature-flags"` | OpenFeature domain; must match `<OpenFeatureProvider domain>` |
|
|
86
|
+
|
|
87
|
+
`isLoading` is `true` until the first response is installed in OpenFeature and again while a
|
|
88
|
+
refetch is in flight; it is `false` when skipped or after an error. Every hook reading the same
|
|
89
|
+
`basePath` shares one cache entry, so a single instance that listens to the socket (usually the
|
|
90
|
+
root `OpenFeatureProvider` bridge) is enough; other screens can pass `socket: null`. The last
|
|
91
|
+
unmounted `useTerrenoFeatureFlags` for a domain restores OpenFeature's NOOP provider.
|
|
92
|
+
|
|
93
|
+
`@openfeature/web-sdk` (and `@openfeature/react-sdk` for the React hooks) are optional peer
|
|
94
|
+
dependencies of `@terreno/syncdb`; add them to apps that import `@terreno/syncdb/flags`.
|
|
63
95
|
|
|
64
96
|
## OpenFeature integration
|
|
65
97
|
|
|
66
|
-
`FeatureFlagsApp` registers an OpenFeature **server** provider (`MongoFeatureFlagProvider`) on a dedicated domain (default `"feature-flags"`) so the global default provider is unchanged. The authenticated bulk endpoint **`GET {basePath}/flagConfiguration`** returns a `FlagConfiguration`-compatible map (suitable for `TypedInMemoryProvider` on the client). Disabled **boolean** flags are included and resolve to `off`, so a client can tell a turned-off flag from one that was never created. Disabled variant flags and archived flags are omitted. The legacy **`GET {basePath}/evaluate`** response shape is unchanged but is **deprecated**: responses include `Deprecation: true` and a `Sunset` HTTP-date header (~90 days). Prefer `useTerrenoFeatureFlags` / `useFeatureFlags` from `@terreno/
|
|
98
|
+
`FeatureFlagsApp` registers an OpenFeature **server** provider (`MongoFeatureFlagProvider`) on a dedicated domain (default `"feature-flags"`) so the global default provider is unchanged. The authenticated bulk endpoint **`GET {basePath}/flagConfiguration`** returns a `FlagConfiguration`-compatible map (suitable for `TypedInMemoryProvider` on the client). Disabled **boolean** flags are included and resolve to `off`, so a client can tell a turned-off flag from one that was never created. Disabled variant flags and archived flags are omitted. The legacy **`GET {basePath}/evaluate`** response shape is unchanged but is **deprecated**: responses include `Deprecation: true` and a `Sunset` HTTP-date header (~90 days). Prefer `useTerrenoFeatureFlags` / `useFeatureFlags` from `@terreno/syncdb/flags`, or migrate direct HTTP callers to `/flagConfiguration`. The server logs this advice once per process on the first `/evaluate` call.
|
|
67
99
|
|
|
68
100
|
### `defaultVariant` on `FeatureFlag`
|
|
69
101
|
|
|
@@ -81,6 +113,15 @@ Consumers may augment `@openfeature/core` with `BooleanFlagKey` / `StringFlagKey
|
|
|
81
113
|
|
|
82
114
|
Pass `liveUpdates: { socketIoServer: io | () => io }` to broadcast on Mongoose change streams. **Requires MongoDB as a replica set** (single-node replset is fine). Emits a socket event (default `featureFlagsChanged`, payload `{ key }`) and `PROVIDER_CONFIGURATION_CHANGED` on the server provider. All authenticated subscribers receive the same event name; payload is only the flag key (same keys are already exposed per user via `/flagConfiguration`).
|
|
83
115
|
|
|
116
|
+
Emit on the Socket.io server that hosts the syncdb socket — `RealtimeApp.getIo()` — so clients
|
|
117
|
+
receive the push on the one socket they already hold:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const realtimeApp = new RealtimeApp({/* ... */});
|
|
121
|
+
app.register(realtimeApp);
|
|
122
|
+
app.register(new FeatureFlagsApp({liveUpdates: {socketIoServer: () => realtimeApp.getIo()}}));
|
|
123
|
+
```
|
|
124
|
+
|
|
84
125
|
## Exports
|
|
85
126
|
|
|
86
127
|
### Classes
|
|
@@ -237,8 +237,8 @@ always hides payload regardless of the hook.
|
|
|
237
237
|
Widgets are registered in the built-in registry — enable via `JobsApp.adminContribution()` only.
|
|
238
238
|
The admin shell lists Jobs under **Platform** (not Screens). Hosts still contribute
|
|
239
239
|
`name: "jobs"`; do not add a second sidebar link.
|
|
240
|
-
Custom screens
|
|
241
|
-
admin HTTP internally (not via a public `useJobsDashboardApi` export).
|
|
240
|
+
Custom screens render inside the app's `AdminProvider` (auth binding); jobs screens wire
|
|
241
|
+
admin HTTP through its fetch client internally (not via a public `useJobsDashboardApi` export).
|
|
242
242
|
|
|
243
243
|
## GCP Cloud Tasks (`@terreno/jobs/runners/gcpCloudTasks`)
|
|
244
244
|
|