@terreno/mcp 57.3.0 → 57.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/bootstrap.d.ts +0 -18
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -2428
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/dist/docs/resources/syncdb.md +4 -1
- package/dist/docs/upgrades/57.4.0.md +140 -0
- package/dist/docs/versioned/next/README.md +3 -0
- package/dist/docs/versioned/next/explanation/README.md +4 -1
- package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/dist/docs/versioned/next/explanation/charts.md +29 -0
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/dist/docs/versioned/next/explanation/organizations.md +88 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/dist/docs/versioned/next/explanation/positioning.md +3 -3
- package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
- package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/dist/docs/versioned/next/how-to/README.md +10 -0
- package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
- package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/dist/docs/versioned/next/how-to/circleci.md +136 -27
- package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
- package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/dist/docs/versioned/next/reference/README.md +4 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
- package/dist/docs/versioned/next/reference/admin-config.md +7 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
- package/dist/docs/versioned/next/reference/ai.md +2 -0
- package/dist/docs/versioned/next/reference/announcements.md +179 -0
- package/dist/docs/versioned/next/reference/api.md +343 -4
- package/dist/docs/versioned/next/reference/comms.md +33 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
- package/dist/docs/versioned/next/reference/jobs.md +319 -0
- package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
- package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/dist/docs/versioned/next/reference/syncdb.md +38 -12
- package/dist/docs/versioned/next/reference/test.md +57 -6
- package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
- package/dist/docs/versioned/next/reference/ui.md +257 -3
- package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/dist/docs/versioned/next/tasks/announcements.md +173 -0
- package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
- package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
- package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/dist/docs/versioned/next/tutorials/README.md +1 -1
- package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
- package/dist/help/excerpt.d.ts +2 -0
- package/dist/help/excerpt.d.ts.map +1 -0
- package/dist/help/excerpt.js +8 -0
- package/dist/help/excerpt.js.map +1 -0
- package/dist/help/updateNotes.d.ts +42 -0
- package/dist/help/updateNotes.d.ts.map +1 -0
- package/dist/help/updateNotes.js +235 -0
- package/dist/help/updateNotes.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts +1 -0
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +1 -0
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/packageGuidelines.d.ts +2 -2
- package/dist/packageGuidelines.d.ts.map +1 -1
- package/dist/packageGuidelines.js +2 -8
- package/dist/packageGuidelines.js.map +1 -1
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +3 -0
- package/dist/resources.js.map +1 -1
- package/dist/search/chunker.d.ts +1 -0
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/chunker.js +1 -0
- package/dist/search/chunker.js.map +1 -1
- package/dist/search/docIndex.d.ts +1 -5
- package/dist/search/docIndex.d.ts.map +1 -1
- package/dist/search/docIndex.js +1 -0
- package/dist/search/docIndex.js.map +1 -1
- package/dist/search/docVersion.d.ts +2 -1
- package/dist/search/docVersion.d.ts.map +1 -1
- package/dist/search/docVersion.js +3 -1
- package/dist/search/docVersion.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +113 -2
- package/dist/tools.js.map +1 -1
- package/package.json +28 -27
- package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/src/docs/resources/syncdb.md +4 -1
- package/src/docs/upgrades/57.4.0.md +140 -0
- package/src/docs/versioned/next/README.md +3 -0
- package/src/docs/versioned/next/explanation/README.md +4 -1
- package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/src/docs/versioned/next/explanation/charts.md +29 -0
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/src/docs/versioned/next/explanation/organizations.md +88 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/src/docs/versioned/next/explanation/positioning.md +3 -3
- package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/src/docs/versioned/next/explanation/source-rules.md +1 -1
- package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/src/docs/versioned/next/how-to/README.md +10 -0
- package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/src/docs/versioned/next/how-to/audit-log.md +85 -0
- package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/src/docs/versioned/next/how-to/circleci.md +136 -27
- package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/src/docs/versioned/next/implementationPlans/README.md +8 -5
- package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/src/docs/versioned/next/reference/README.md +4 -0
- package/src/docs/versioned/next/reference/admin-backend.md +83 -5
- package/src/docs/versioned/next/reference/admin-config.md +7 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/src/docs/versioned/next/reference/admin-spa.md +18 -1
- package/src/docs/versioned/next/reference/ai.md +2 -0
- package/src/docs/versioned/next/reference/announcements.md +179 -0
- package/src/docs/versioned/next/reference/api.md +343 -4
- package/src/docs/versioned/next/reference/comms.md +33 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/src/docs/versioned/next/reference/environment-variables.md +29 -0
- package/src/docs/versioned/next/reference/jobs.md +319 -0
- package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/src/docs/versioned/next/reference/mcp-server.md +2 -43
- package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/src/docs/versioned/next/reference/syncdb.md +38 -12
- package/src/docs/versioned/next/reference/test.md +57 -6
- package/src/docs/versioned/next/reference/ui-performance.md +2 -1
- package/src/docs/versioned/next/reference/ui.md +257 -3
- package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/src/docs/versioned/next/tasks/announcements.md +173 -0
- package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/src/docs/versioned/next/tasks/job-queues.md +144 -0
- package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/src/docs/versioned/next/tasks/notification-center.md +102 -0
- package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/src/docs/versioned/next/tutorials/README.md +1 -1
- package/src/docs/versioned/next/tutorials/getting-started.md +62 -22
|
@@ -9,24 +9,32 @@ REST API framework built on Express and Mongoose. Provides modelRouter (CRUD end
|
|
|
9
9
|
- [Authentication](#authentication)
|
|
10
10
|
- [Model Schema Conventions](#model-schema-conventions)
|
|
11
11
|
- [Mongoose Plugins](#mongoose-plugins)
|
|
12
|
+
- [Organizations](#organizations)
|
|
12
13
|
- [Request Validation](#request-validation)
|
|
13
14
|
- [Middleware](#middleware)
|
|
14
15
|
- [Logging & Tracing](#logging--tracing)
|
|
15
16
|
- [Extensibility](#extensibility)
|
|
17
|
+
- [In-app notifications](#in-app-notifications)
|
|
16
18
|
- [Webhooks & Notifications](#webhooks--notifications)
|
|
17
19
|
- [HTTP Client](#http-client)
|
|
18
20
|
- [Utilities](#utilities)
|
|
19
21
|
- [Script Helpers](#script-helpers)
|
|
22
|
+
- [Migrations](#migrations)
|
|
20
23
|
|
|
21
24
|
## Key exports
|
|
22
25
|
|
|
23
26
|
- `TerrenoApp`, `setupServer`, `modelRouter`, `Permissions`, `OwnerQueryFilter`
|
|
27
|
+
- Organizations: `Organization`, `Membership`, `OrgsApp`, `orgScopedPlugin`,
|
|
28
|
+
`orgContextMiddleware`, `getOrgContext`, `OrgQueryFilter`,
|
|
29
|
+
`Permissions.IsOrganizationMember`, `organizationSlugFromName`
|
|
30
|
+
- `AuditApp`, `createAuditEventModel`, `persistRbacAuditToAuditEvent`
|
|
24
31
|
- `registerMCPTool`, `getMCPRegistry`
|
|
25
32
|
- `APIError`, `logger`, `asyncHandler`, `authenticateMiddleware`
|
|
26
33
|
- Logging: `logger`, `createScopedLogger`, `createFeatureFlaggedLogger`, `setupLogging`, `formatLogContextSuffix`
|
|
27
34
|
- Correlation: `runWithRequestContext`, `getCurrentLogContext`, `requestContextMiddleware`, `REQUEST_CONTEXT_ATTRIBUTE_NAMES`
|
|
28
35
|
- `createOpenApiBuilder`
|
|
29
36
|
- Seeds: `runSeeds`, `runSeedCli`, `seedBetterAuthUser`
|
|
37
|
+
- Migrations: `assertMigrationsAllowed`, `runMigrations`, `runDownMigrations`, `checkMigrationFiles`, `resolveMigrationDir`, `exerciseReversibleMigrations`, `MIGRATIONS_COLLECTION` (`terreno-migrate` bin)
|
|
30
38
|
- `githubUserPlugin`, `setupGitHubAuth`, `addGitHubAuthRoutes`
|
|
31
39
|
- `AuthToken`, `AUTH_TOKEN_TTL` (hashed single-use password-reset / email-verification tokens)
|
|
32
40
|
- Mongoose plugins: `findExactlyOne`, `findOneOrNone`, `upsertPlugin`, `DateOnly`, `emailVerificationPlugin`
|
|
@@ -77,7 +85,16 @@ const app = new TerrenoApp({userModel: User})
|
|
|
77
85
|
- `register(registration)` — Register `ModelRouterRegistration` or `TerrenoPlugin`
|
|
78
86
|
- `addMiddleware(fn)` — Add Express middleware
|
|
79
87
|
- `build()` — Build Express app without listening
|
|
80
|
-
|
|
88
|
+
`start()` — Build and start server. Pass `httpServer` to attach to an already-listening `http.Server` instead of binding a new port (Cloud Run: bind `PORT` before MongoDB connect).
|
|
89
|
+
|
|
90
|
+
`migrations.runOnStart` defaults to **false**. When `true`, `start()` binds listen first, then runs `ensureSyncIndexes`, then wet `up`. Production still requires `ALLOW_MIGRATIONS=true` (boot counts as `--force`). Omitted `migrations` never reads `terreno_migrations`.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
new TerrenoApp({
|
|
94
|
+
userModel: User,
|
|
95
|
+
migrations: {dir: "./migrations", runOnStart: true},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
81
98
|
|
|
82
99
|
### HTTP rate limiting
|
|
83
100
|
|
|
@@ -116,6 +133,41 @@ new TerrenoApp({
|
|
|
116
133
|
|
|
117
134
|
`mcpServiceTokens: true` is `{enabled: true}`. When enabled, TerrenoApp mounts the self-serve routes (passing its OpenAPI bundle) and sets `mcpServiceTokens` on MCP auth. Operator steps: [Connect an MCP client with a service token](../how-to/connect-mcp-service-token.md).
|
|
118
135
|
|
|
136
|
+
### Audit log (`AuditApp`)
|
|
137
|
+
|
|
138
|
+
Opt-in append-only log. Register the plugin; importing `@terreno/api` does **not** compile `AuditEvent` onto the default mongoose connection.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import {
|
|
142
|
+
AuditApp,
|
|
143
|
+
persistRbacAuditToAuditEvent,
|
|
144
|
+
TerrenoApp,
|
|
145
|
+
modelRouter,
|
|
146
|
+
} from "@terreno/api";
|
|
147
|
+
|
|
148
|
+
new TerrenoApp({userModel: User})
|
|
149
|
+
.register(new AuditApp()) // optional {retentionDays: 90}
|
|
150
|
+
.register(
|
|
151
|
+
modelRouter("/todos", Todo, {
|
|
152
|
+
audit: true, // or {redact: ["ssn"]}
|
|
153
|
+
permissions: {/* ... */},
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
.start();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Surface | How it writes |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `modelRouter` | `audit: true` or `{redact?: string[]}` after successful HTTP create/update/delete and array push/update/remove |
|
|
162
|
+
| `AdminApp` | Auto when `AuditApp` is registered (`source: "admin"`). `onAdminAudit` is extra |
|
|
163
|
+
| RBAC | `createAccess({auditSink: persistRbacAuditToAuditEvent})` (`source: "rbac"`) |
|
|
164
|
+
|
|
165
|
+
HTTP is list+read only: `GET /audit-events` with `Permissions.IsAdmin`. Empty create/update/delete permission arrays mean POST/PATCH/DELETE return **405**, including `/admin/audit-events` (`admin.adminPermissions`). Non-admin list is also **405** (`permissionMiddleware`). There is no `isDeletedPlugin`; rows are not soft-deleted.
|
|
166
|
+
|
|
167
|
+
`createAuditEventModel(connection, {retentionDays?})` is the factory for tests and scripts. Never audit `AuditEvent` itself. Diffs run on the request; persist is fire-and-forget (the HTTP handler does not await Mongo). Recorder failures (including serialization) log and leave the mutation 2xx. Secret field names (`password`, `hash`, `salt`, `token`, `secret`, `refreshToken`) are stripped at every object and array depth, including compound keys such as `tokenHash`. AdminApp diffs also omit `hiddenFields` and `excludeFields`.
|
|
168
|
+
|
|
169
|
+
When `GCP_TASKS_AUDIT_QUEUE`, `AUDIT_TASKS_URL`, `GCP_PROJECT`, `GCP_LOCATION`, and `AUDIT_TASKS_SECRET` are set, `AuditApp` enqueues the write with Cloud Tasks (`@google-cloud/tasks` optional install) instead of writing Mongo in-process, and mounts `POST /internal/audit-events` (header `X-Terreno-Audit-Secret`) so the worker persists. Pass `enqueue` yourself to use any other queue. Default retention is forever (no TTL index). `new AuditApp({retentionDays: n})` for `n > 0` replaces the plugin `{created: 1}` index with `{created: 1, expireAfterSeconds: n * 86400}`. Drop that index yourself if you later remove TTL. Operator steps: [Enable the framework audit log](../how-to/audit-log.md).
|
|
170
|
+
|
|
119
171
|
### setupServer (Legacy)
|
|
120
172
|
|
|
121
173
|
Callback-based pattern:
|
|
@@ -137,6 +189,40 @@ Both patterns create the same middleware stack (CORS, auth, logging, OpenAPI). H
|
|
|
137
189
|
|
|
138
190
|
`modelRouter("/path", Model, options)` writes one catalog record per route path. MCP, realtime, and sync surfaces read that record; TerrenoApp calls `replaceCollectionOptions` once when access control is injected. Test helpers `clearMCPRegistry`, `clearRealtimeRegistry`, and `clearSyncRegistry` clear the entire catalog.
|
|
139
191
|
|
|
192
|
+
## modelRouter actions
|
|
193
|
+
|
|
194
|
+
Named GET/POST operations on a collection or document. Use these instead of `app.get` /
|
|
195
|
+
`app.post` / `router.get` / `router.post` for application APIs.
|
|
196
|
+
|
|
197
|
+
See [modelRouter actions](../explanation/model-router-actions.md). Example:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
export const todoRouter = modelRouter("/todos", Todo, {
|
|
201
|
+
collectionActions: {
|
|
202
|
+
bulkComplete: {
|
|
203
|
+
method: "POST",
|
|
204
|
+
permissions: [Permissions.IsAuthenticated],
|
|
205
|
+
body: z.object({ids: z.array(z.string()).min(1)}).strict(),
|
|
206
|
+
handler: async ({body, user}) => {
|
|
207
|
+
return {matched: 0, modified: 0};
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
instanceActions: {
|
|
212
|
+
markComplete: {
|
|
213
|
+
method: "POST",
|
|
214
|
+
permissions: [Permissions.IsOwner],
|
|
215
|
+
handler: async ({doc}) => {
|
|
216
|
+
return doc;
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
permissions: { /* CRUD */ },
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Do not add `endpoints: (router) => { router.get(...) }` when an action fits.
|
|
225
|
+
|
|
140
226
|
## MCP tools
|
|
141
227
|
|
|
142
228
|
Opt a model into Model Context Protocol tools with `mcp` on `modelRouter`. `TerrenoApp` mounts `POST /mcp` when any model has `mcp` or a custom tool is registered.
|
|
@@ -322,6 +408,12 @@ Better Auth password and deletes those sessions when `BetterAuthApp` is register
|
|
|
322
408
|
Optional `authMailTemplates` overrides
|
|
323
409
|
subject/text/html per template id.
|
|
324
410
|
|
|
411
|
+
In-process seed helpers (for example `seedBetterAuthUserInProcess`) should pass
|
|
412
|
+
`disableRateLimit: true` on that throwaway Better Auth instance. Better Auth
|
|
413
|
+
turns its built-in limiter on when `NODE_ENV=production`, so seeding six demo
|
|
414
|
+
users during a preview smoke test otherwise 429s on the fourth signup. Do not
|
|
415
|
+
set this on the public `BetterAuthApp`.
|
|
416
|
+
|
|
325
417
|
**Endpoints (when enabled):**
|
|
326
418
|
- `POST /api/auth/signup/email` — Email/password signup
|
|
327
419
|
- `POST /api/auth/signin/email` — Email/password signin
|
|
@@ -387,6 +479,20 @@ Field descriptions appear in:
|
|
|
387
479
|
|
|
388
480
|
@terreno/api provides several Mongoose plugins to extend model functionality with common patterns.
|
|
389
481
|
|
|
482
|
+
### orgScopedPlugin
|
|
483
|
+
|
|
484
|
+
Adds a required indexed `organizationId` (ref `Organization`) to consumer schemas. Save fails
|
|
485
|
+
without an organization. Use this on tenant-scoped models; query scoping lands with org context.
|
|
486
|
+
After creation, `organizationId` is immutable — REST PATCH, admin writes, sync updates, and
|
|
487
|
+
direct `doc.save()` / `updateOne()` attempts to retarget another organization return HTTP 400
|
|
488
|
+
with title `organizationId cannot be changed`.
|
|
489
|
+
|
|
490
|
+
``````typescript
|
|
491
|
+
import {orgScopedPlugin} from "@terreno/api";
|
|
492
|
+
|
|
493
|
+
projectSchema.plugin(orgScopedPlugin);
|
|
494
|
+
``````
|
|
495
|
+
|
|
390
496
|
### findExactlyOne & findOneOrNone
|
|
391
497
|
|
|
392
498
|
**Critical:** Never use `Model.findOne` directly. Use these plugins instead to prevent ambiguous queries.
|
|
@@ -574,6 +680,138 @@ export const addDefaultPlugins = (schema) => {
|
|
|
574
680
|
todoSchema.plugin(addDefaultPlugins);
|
|
575
681
|
``````
|
|
576
682
|
|
|
683
|
+
## Organizations
|
|
684
|
+
|
|
685
|
+
Organizations are **opt-in**. Existing and single-tenant apps omit them: do not pass
|
|
686
|
+
`organizations: true` to `createAccess` or `TerrenoApp`, and do not register `OrgsApp`.
|
|
687
|
+
New apps from `create-terreno-app` enable organizations by default.
|
|
688
|
+
|
|
689
|
+
| App kind | What to do |
|
|
690
|
+
| --- | --- |
|
|
691
|
+
| Existing / single-tenant | Leave `createAccess` and `TerrenoApp` without `organizations`. No `operator` seed, no membership grants, no `/orgs` routes. |
|
|
692
|
+
| New app (bootstrap) | Generated `backend/src/access.ts` uses `createAccess({ organizations: true })`. `TerrenoApp({ organizations: true, accessControl: access })` mounts `OrgsApp`. Seed creates a default org and makes `admin@example.com` an `operator` and `org-admin`. |
|
|
693
|
+
| Existing app adopting orgs | `createAccess({ organizations: true })`, `await access.roles.seedDefaults()`, and `TerrenoApp({ organizations: true, accessControl })` or `.register(new OrgsApp({ access, userModel }))`. |
|
|
694
|
+
|
|
695
|
+
`Organization` and `Membership` models register on first use, not when you import `@terreno/api`.
|
|
696
|
+
|
|
697
|
+
Native `Organization` and `Membership` models live in `@terreno/api`.
|
|
698
|
+
|
|
699
|
+
| Model | Fields |
|
|
700
|
+
| --- | --- |
|
|
701
|
+
| `Organization` | `name` (required), `slug` (unique, generated from name), `ownerId`, `settings` (app-defined; Mixed until you register a nested schema), `disabled` |
|
|
702
|
+
| `Membership` | `organizationId`, `userId`, `roleName` (`org-admin` \| `member`, default `member`), `status` (`active` \| `suspended`) |
|
|
703
|
+
|
|
704
|
+
Compound unique index: `(organizationId, userId)`. Duplicate memberships throw a Mongo duplicate-key error.
|
|
705
|
+
|
|
706
|
+
`Membership` statics: `findActiveForUser`, `isOrgAdmin`, `isMember` (active rows only). Per-org
|
|
707
|
+
`org-admin` is stored on Membership, not on `user.roles`.
|
|
708
|
+
|
|
709
|
+
### Organization.settings
|
|
710
|
+
|
|
711
|
+
Do not add top-level fields to the native `Organization` model. Extend `settings`:
|
|
712
|
+
|
|
713
|
+
| Helper | Role |
|
|
714
|
+
| --- | --- |
|
|
715
|
+
| `createOrganizationSettingsSchema(definition)` | Nested schema with `_id: false` and `strict: "throw"` |
|
|
716
|
+
| `registerOrganizationSettings(schema)` | Validate settings on create/save/PATCH. Call with no argument to clear |
|
|
717
|
+
| `organizationSettingsOf(organization)` | Type-safe read; defaults to `{}` |
|
|
718
|
+
| `OrganizationSettings` | Empty interface for `declare module "@terreno/api"` merging |
|
|
719
|
+
|
|
720
|
+
Pass `settingsSchema` on `TerrenoApp({ organizations: { settingsSchema } })` or `new OrgsApp({ settingsSchema })`. Keep settings fields optional or defaulted so existing documents still save. See [Add organizations](../how-to/add-organizations.md#3-type-organization-settings).
|
|
721
|
+
|
|
722
|
+
### OrgsApp routes
|
|
723
|
+
|
|
724
|
+
| Method | Path | Required access |
|
|
725
|
+
| --- | --- | --- |
|
|
726
|
+
| `POST` | `/orgs` | `organization:create` |
|
|
727
|
+
| `GET` | `/orgs` | `organization:list` |
|
|
728
|
+
| `GET` | `/orgs/mine` | Active org-admin membership, operator, or superadmin (disabled orgs omitted) |
|
|
729
|
+
| `GET` | `/orgs/:id` | `organization:read` in that org |
|
|
730
|
+
| `PATCH` | `/orgs/:id` | `organization:update`; disabling also requires `organization:disable` |
|
|
731
|
+
| `DELETE` | `/orgs/:id` | `organization:delete` |
|
|
732
|
+
| `GET` / `POST` | `/orgs/:id/members` | `organization:manageMembers` |
|
|
733
|
+
| `PATCH` / `DELETE` | `/orgs/:id/members/:memberId` | `organization:manageMembers`; cannot remove or demote the last org-admin |
|
|
734
|
+
|
|
735
|
+
Creating an organization does not create a membership automatically. Member
|
|
736
|
+
attach accepts an existing `userId` or email (case-insensitive); it does not send
|
|
737
|
+
an invitation. Disabling or deleting an organization suspends its memberships.
|
|
738
|
+
Member attach, update, and remove routes reject disabled organizations with 403.
|
|
739
|
+
Operators may still `GET` and `PATCH /orgs/:id` on a disabled organization to
|
|
740
|
+
re-enable it; `GET /orgs` continues to list disabled organizations.
|
|
741
|
+
|
|
742
|
+
### Request organization context
|
|
743
|
+
|
|
744
|
+
Tenant-scoped routes run `orgContextMiddleware({required: true})` after auth, then
|
|
745
|
+
`queryFilter: OrgQueryFilter`.
|
|
746
|
+
|
|
747
|
+
| Condition | Result |
|
|
748
|
+
| --- | --- |
|
|
749
|
+
| `X-Organization-Id` present, organization disabled | 403 `Organization is disabled` |
|
|
750
|
+
| `X-Organization-Id` present, caller is `operator`/`superadmin` or an active member, org enabled | `req.organization` set |
|
|
751
|
+
| `X-Organization-Id` present, caller is not a member and not a platform org actor | 403 |
|
|
752
|
+
| Tenant-scoped route, `operator`/`superadmin`, header omitted | 400 |
|
|
753
|
+
| Caller is `org-admin` of exactly one enabled org, header omitted | that org is inferred |
|
|
754
|
+
| Caller is `org-admin` of many orgs, header omitted | 400 |
|
|
755
|
+
| Otherwise on tenant-scoped routes | 403 |
|
|
756
|
+
|
|
757
|
+
`OrgQueryFilter` always ANDs `{organizationId: context.id}`. Client `organizationId` query params
|
|
758
|
+
and `$or` cannot list another org.
|
|
759
|
+
|
|
760
|
+
`user.admin` is not operator. Platform org actors are `user.roles` containing `operator` or
|
|
761
|
+
`superadmin`.
|
|
762
|
+
|
|
763
|
+
Use `Permissions.IsOrganizationMember` on read, update, and delete methods for
|
|
764
|
+
tenant models. It requires the object's `organizationId` to match the active
|
|
765
|
+
request organization context (AsyncLocalStorage from `orgContextMiddleware`, and
|
|
766
|
+
from `X-Organization-Id` on `POST /sync/mutate`) for every caller. Platform actors (`operator` / `superadmin`) then pass; members
|
|
767
|
+
must also hold an active Membership in that organization. Direct `GET` / `PATCH`
|
|
768
|
+
by document id does not apply `OrgQueryFilter`, so this object-level check is
|
|
769
|
+
what blocks cross-tenant reads and writes. `getOrgContext()` exposes the
|
|
770
|
+
resolved organization to create hooks so they can overwrite client-provided
|
|
771
|
+
organization ids. Duplicate organization names that generate the same slug,
|
|
772
|
+
including slugs still held by soft-deleted organizations, return **409**
|
|
773
|
+
`Organization name already in use` on `POST /orgs` and on rename
|
|
774
|
+
via `PATCH /orgs/:id`. See [Add organizations](../how-to/add-organizations.md)
|
|
775
|
+
for complete route wiring.
|
|
776
|
+
|
|
777
|
+
### Organization RBAC
|
|
778
|
+
|
|
779
|
+
`terrenoStatements.organization` actions: `create`, `list`, `read`, `update`, `delete`,
|
|
780
|
+
`manageMembers`, `disable`.
|
|
781
|
+
|
|
782
|
+
| Role | Where it lives | Organization grants |
|
|
783
|
+
| --- | --- | --- |
|
|
784
|
+
| `superadmin` | `user.roles` | `*` (includes every organization action) |
|
|
785
|
+
| `operator` | seeded locked `user.roles` when `createAccess({ organizations: true })` | all `organization` actions, plus `admin:access` and `user:list\|read\|update` |
|
|
786
|
+
| `org-admin` | `Membership.roleName` in the current org context | `organization:read\|update\|manageMembers` and `admin:access` |
|
|
787
|
+
| `admin` | `user.roles` | none of `organization:*` |
|
|
788
|
+
|
|
789
|
+
`createAccess({ organizations: true })` prepends a membership permission source (`ttlMs: 0`) and
|
|
790
|
+
seeds the locked `operator` role. Without that flag, membership `org-admin` grants nothing extra
|
|
791
|
+
and `operator` is not seeded. Putting `org-admin` on `user.roles` does not grant those permissions.
|
|
792
|
+
Permission cache keys include the current organization id and membership role so org-admin grants
|
|
793
|
+
do not leak across orgs.
|
|
794
|
+
|
|
795
|
+
``````typescript
|
|
796
|
+
import {createAccess, Membership, Organization, terrenoStatements} from "@terreno/api";
|
|
797
|
+
|
|
798
|
+
const access = createAccess({
|
|
799
|
+
connection: mongoose.connection,
|
|
800
|
+
organizations: true,
|
|
801
|
+
statements: terrenoStatements,
|
|
802
|
+
userModel: User,
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
const org = await Organization.create({name: "Acme Corp", ownerId: user._id});
|
|
806
|
+
// org.slug === "acme-corp"
|
|
807
|
+
|
|
808
|
+
await Membership.create({
|
|
809
|
+
organizationId: org._id,
|
|
810
|
+
roleName: "org-admin",
|
|
811
|
+
userId: user._id,
|
|
812
|
+
});
|
|
813
|
+
``````
|
|
814
|
+
|
|
577
815
|
## Request Validation
|
|
578
816
|
|
|
579
817
|
@terreno/api provides runtime validation of incoming requests against OpenAPI schemas using [AJV](https://ajv.js.org/). Validation is opt-in and can be enabled globally or per-route.
|
|
@@ -617,7 +855,9 @@ setupServer({
|
|
|
617
855
|
|
|
618
856
|
### Using with modelRouter
|
|
619
857
|
|
|
620
|
-
When validation is enabled globally, modelRouter automatically validates create and update requests
|
|
858
|
+
When validation is enabled globally, modelRouter automatically validates create and update requests.
|
|
859
|
+
|
|
860
|
+
Create (POST) enforces Mongoose required fields. Update (PATCH) is partial: present fields are type-checked, but omitted required fields are allowed so a client can send `{completed: true}` without repeating `title`.
|
|
621
861
|
|
|
622
862
|
``````typescript
|
|
623
863
|
import {modelRouter, Permissions} from "@terreno/api";
|
|
@@ -1121,6 +1361,79 @@ setupServer({
|
|
|
1121
1361
|
- Optional/configurable functionality
|
|
1122
1362
|
- Clean separation of concerns
|
|
1123
1363
|
|
|
1364
|
+
## In-app notifications
|
|
1365
|
+
|
|
1366
|
+
Owner-scoped inbox and channel preferences via `NotificationsApp` (`ConsentApp` pattern).
|
|
1367
|
+
This is separate from inbound webhooks and outbound Slack/Chat/Zoom notifiers below.
|
|
1368
|
+
|
|
1369
|
+
### Register
|
|
1370
|
+
|
|
1371
|
+
```typescript
|
|
1372
|
+
import {NotificationsApp, getNotificationService, notificationsBeforeSend} from "@terreno/api";
|
|
1373
|
+
|
|
1374
|
+
new TerrenoApp({userModel: User}).register(
|
|
1375
|
+
new NotificationsApp({
|
|
1376
|
+
getComms: getCommsService, // optional; duck-typed, no @terreno/comms import in api
|
|
1377
|
+
retainDays: 0,
|
|
1378
|
+
userModel: User,
|
|
1379
|
+
})
|
|
1380
|
+
);
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
### Collections
|
|
1384
|
+
|
|
1385
|
+
| Model | Route | Sync | Client create |
|
|
1386
|
+
|---|---|---|---|
|
|
1387
|
+
| `Notification` | `/notifications` | owner | **No** (`create: []`) |
|
|
1388
|
+
| `NotificationPreference` | `/notification-preferences` | owner | Yes (lazy defaults) |
|
|
1389
|
+
|
|
1390
|
+
`Notification` fields: `ownerId`, `title`, `body`, `href?`, `kind?`, `readAt?` (null = unread),
|
|
1391
|
+
`archivedAt?` (null = active inbox).
|
|
1392
|
+
Index: `{ownerId: 1, created: -1}`.
|
|
1393
|
+
|
|
1394
|
+
`NotificationPreference` fields: `ownerId` (unique among non-deleted rows), `inapp`, `mail`,
|
|
1395
|
+
`push`, `sms` (default `true`). Missing preference row = all channels on. Preference updates
|
|
1396
|
+
accept only the four channel booleans; `ownerId` is immutable. Soft-deleting a preference
|
|
1397
|
+
row does not block a later create for the same owner.
|
|
1398
|
+
|
|
1399
|
+
`notify()` comms fan-out isolates mail, SMS, and push: a rejection from one provider is
|
|
1400
|
+
logged and does not skip later channels.
|
|
1401
|
+
|
|
1402
|
+
### `notify(input)`
|
|
1403
|
+
|
|
1404
|
+
Server-only seam. Writes the inbox when `inapp` is on, then optionally calls
|
|
1405
|
+
`getComms().sendMail` / `sendSms` / `sendPushToUser` when that channel is on and a
|
|
1406
|
+
destination exists. Comms errors after the inbox write are logged and do not fail `notify()`.
|
|
1407
|
+
|
|
1408
|
+
### HTTP
|
|
1409
|
+
|
|
1410
|
+
| Method | Path | Notes |
|
|
1411
|
+
|---|---|---|
|
|
1412
|
+
| GET/PATCH/DELETE | `/notifications/:id` + list | Owner-scoped; **create disabled** |
|
|
1413
|
+
| POST | `/notifications/mark-all-read` | Sets `readAt` on caller's unread rows |
|
|
1414
|
+
| CRUD | `/notification-preferences` | Owner-scoped sync |
|
|
1415
|
+
|
|
1416
|
+
PATCH on notifications: `readAt` (ISO date or `null` to unread) and `archivedAt` (ISO date
|
|
1417
|
+
or `null`). Other keys are stripped. Dismiss sets `archivedAt` via syncdb update so the
|
|
1418
|
+
row stays in the snapshot. DELETE remains a retention tombstone (`isDeletedPlugin`), not
|
|
1419
|
+
the inbox archive path.
|
|
1420
|
+
|
|
1421
|
+
The example backend uses todo router lifecycle hooks to call `notify()`: create emits
|
|
1422
|
+
`Todo added`, the first incomplete-to-complete update emits `Todo completed`, and delete
|
|
1423
|
+
emits `Todo deleted`.
|
|
1424
|
+
|
|
1425
|
+
### `notificationsBeforeSend`
|
|
1426
|
+
|
|
1427
|
+
Duck-typed hook for `CommsApp({beforeSend})`. Cancels when the user's preference for that
|
|
1428
|
+
channel is `false`. Never cancels `verification`.
|
|
1429
|
+
|
|
1430
|
+
### Retention
|
|
1431
|
+
|
|
1432
|
+
`retainDays` default `0` (no sweep). When `retainDays > 0`, `sweepExpired()` tombstones rows
|
|
1433
|
+
with `created` older than N days. No Mongo TTL index.
|
|
1434
|
+
|
|
1435
|
+
How-to: [In-app notifications](../how-to/in-app-notifications.md).
|
|
1436
|
+
|
|
1124
1437
|
## Webhooks & Notifications
|
|
1125
1438
|
|
|
1126
1439
|
JSON and `application/x-www-form-urlencoded` parsers on `TerrenoApp` copy the original
|
|
@@ -1334,6 +1647,20 @@ for (let i = 0; i < 3; i++) {
|
|
|
1334
1647
|
}
|
|
1335
1648
|
``````
|
|
1336
1649
|
|
|
1650
|
+
## Migrations
|
|
1651
|
+
|
|
1652
|
+
`runMigrations({migrations, dryRun, connection, mongoose})` applies pending versioned `up` functions in array order. Dry-run calls `up({dryRun: true})` and does **not** write history. Wet runs insert `{id, checksum, appliedAt}` into Mongo collection `terreno_migrations`. Already-applied ids are skipped; a checksum change after apply throws `Migration checksum mismatch` (409). Apply is serialized with a lock document `_id: "_lock"` in the same collection: wait, heartbeat, steal after **10 minutes**.
|
|
1653
|
+
|
|
1654
|
+
`runDownMigrations({migrations, dryRun, connection, mongoose, steps})` rolls back the last `steps` applied files (default 1 via the CLI). Dry-run calls `down({dryRun: true})` and leaves history in place. Wet deletes the history row after `down`. Missing `down` throws `Migration has no down` (400) and does not change history.
|
|
1655
|
+
|
|
1656
|
+
Files are `migrations/<YYYYMMDDHHmmss>-<slug>.ts`. `checkMigrationFiles({dir})` (alias `loadMigrations`) loads them in filename order without connecting to Mongo; the exported `id` must match the filename stem. Optional `down` is allowed. Duplicate ids and invalid names fail with 400 `APIError`. A missing directory fails with 404 `Migration directory not found`. Paths on Bun's compile-time virtual FS (`$bunfs`) are remapped with `resolveMigrationDir` to `MIGRATIONS_DIR` or `<cwd>/migrations`. `exerciseReversibleMigrations({dir, connect})` applies every file on a connected database, then rolls back in reverse until a file without `down` stops the chain (that id is recorded; earlier files stay applied).
|
|
1657
|
+
|
|
1658
|
+
`assertMigrationsAllowed({isProduction, allowEnv, force, dryRun})` gates wet apply. Dry-run is always allowed. Production wet requires `ALLOW_MIGRATIONS=true` and `--force` (or a later boot/admin Apply equivalent).
|
|
1659
|
+
|
|
1660
|
+
`buildSchemaCatalog({models})` snapshots Mongoose paths, required/unique flags, and indexes. `diffSchemaCatalog({before, after})` marks optional fields and non-unique index adds as safe, and required fields, unique indexes, same-type rename heuristics, removed paths, and same-path type changes as unsafe (fail-closed generate stubs). Mixed diffs still throw, and keep safe index calls as comments in the stub.
|
|
1661
|
+
|
|
1662
|
+
The `@terreno/api` bin `terreno-migrate` runs `check`, `generate`, `status`, `up`, and `down`. See [Run MongoDB migrations](../how-to/run-mongodb-migrations.md).
|
|
1663
|
+
|
|
1337
1664
|
## Script Helpers
|
|
1338
1665
|
|
|
1339
1666
|
### runSeeds and runSeedCli
|
|
@@ -1402,6 +1729,13 @@ cronjob("0 * * * *", async () => {
|
|
|
1402
1729
|
- `"0 0 * * *"` — Daily at midnight
|
|
1403
1730
|
- `"0 */6 * * *"` — Every 6 hours
|
|
1404
1731
|
|
|
1732
|
+
**Process-local only:** `cronjob()` runs inside the current Node process. It does not
|
|
1733
|
+
persist work, retry across crashes, or share state between API and worker replicas. For
|
|
1734
|
+
durable queues, retries, dead-lettering, cron schedules stored in MongoDB, and admin
|
|
1735
|
+
visibility, use [`@terreno/jobs`](jobs.md) — see
|
|
1736
|
+
[Durable background jobs](../how-to/background-jobs.md). Use `wrapScript` (above) for
|
|
1737
|
+
one-shot CLI entrypoints, not queued work.
|
|
1738
|
+
|
|
1405
1739
|
## Deprecations
|
|
1406
1740
|
|
|
1407
1741
|
### transformer (modelRouter option)
|
|
@@ -1459,6 +1793,7 @@ Complete reference of environment variables used by @terreno/api:
|
|
|
1459
1793
|
| `PORT` | No | `3000` | HTTP server port |
|
|
1460
1794
|
| `NODE_ENV` | No | `development` | Environment: `development`, `production`, `test` |
|
|
1461
1795
|
| `MONGO_URI` or `MONGO_CONNECTION` | Yes | — | MongoDB connection string |
|
|
1796
|
+
| `ALLOW_MIGRATIONS` | No | unset | Set to `"true"` plus CLI `--force` for production wet `up`/`down` |
|
|
1462
1797
|
| `ENABLE_SWAGGER` | No | — | Set to `"true"` to enable Swagger UI at `/docs` |
|
|
1463
1798
|
| `WEBSOCKET_PORT` | No | `PORT + 1` | Socket.io server port (if using WebSockets) |
|
|
1464
1799
|
|
|
@@ -1489,8 +1824,11 @@ Complete reference of environment variables used by @terreno/api:
|
|
|
1489
1824
|
| `GCP_PROJECT` | No | — | Google Cloud project ID (for Cloud Tasks, etc.) |
|
|
1490
1825
|
| `GCP_LOCATION` | No | — | GCP region (e.g., "us-central1") |
|
|
1491
1826
|
| `GCP_SERVICE_ACCOUNT_EMAIL` | No | — | Service account email for authentication |
|
|
1492
|
-
| `GCP_TASKS_NOTIFICATIONS_QUEUE` | No | — |
|
|
1493
|
-
| `GCP_TASK_PROCESSOR_QUEUE` | No | — | Cloud Tasks
|
|
1827
|
+
| `GCP_TASKS_NOTIFICATIONS_QUEUE` | No | — | Legacy name in docs/tests only — **not** read by `@terreno/api` or `@terreno/jobs` |
|
|
1828
|
+
| `GCP_TASK_PROCESSOR_QUEUE` | No | — | Legacy name in docs/tests only — configure Cloud Tasks via `GcpCloudTasksRunner` options |
|
|
1829
|
+
| `GCP_TASKS_AUDIT_QUEUE` | No | — | Cloud Tasks queue name for off-process `AuditEvent` writes |
|
|
1830
|
+
| `AUDIT_TASKS_URL` | No | — | Worker URL Cloud Tasks POSTs audit writes to |
|
|
1831
|
+
| `AUDIT_TASKS_SECRET` | No | — | Shared secret for `X-Terreno-Audit-Secret` on the audit worker route |
|
|
1494
1832
|
|
|
1495
1833
|
### Other
|
|
1496
1834
|
|
|
@@ -1519,6 +1857,7 @@ SENTRY_DSN=https://...@sentry.io/...
|
|
|
1519
1857
|
|
|
1520
1858
|
## Learn more
|
|
1521
1859
|
|
|
1860
|
+
- [Enable the framework audit log](../how-to/audit-log.md)
|
|
1522
1861
|
- [How to create a model](../how-to/create-a-model.md)
|
|
1523
1862
|
- [Add GitHub OAuth](../how-to/add-github-oauth.md)
|
|
1524
1863
|
- [Authentication architecture](../explanation/authentication.md)
|
|
@@ -70,6 +70,39 @@ A throwing `beforeSend` is logged and treated as no-op (send continues). Adapter
|
|
|
70
70
|
call `recordDeliveryEvent()` / `recordOptOut()` to update the log and fire
|
|
71
71
|
`onDeliveryEvent` / `onOptOut`.
|
|
72
72
|
|
|
73
|
+
### Notification preferences hook
|
|
74
|
+
|
|
75
|
+
When you register `NotificationsApp`, compose `notificationsBeforeSend` from `@terreno/api`
|
|
76
|
+
into `CommsApp({beforeSend})` so outbound mail, SMS, and push respect per-user channel
|
|
77
|
+
toggles. Missing `NotificationPreference` rows default to all channels on. The
|
|
78
|
+
`verification` channel is never cancelled by this hook.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import {notificationsBeforeSend} from "@terreno/api";
|
|
82
|
+
|
|
83
|
+
new CommsApp({
|
|
84
|
+
beforeSend: async (context) => {
|
|
85
|
+
const pref = await notificationsBeforeSend({
|
|
86
|
+
channel: context.channel,
|
|
87
|
+
userId: context.userId,
|
|
88
|
+
});
|
|
89
|
+
if (pref?.cancel) {
|
|
90
|
+
return {cancel: true};
|
|
91
|
+
}
|
|
92
|
+
return existingBeforeSend?.(context);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`getNotificationService().notify()` fans out to comms independently. Register this hook and
|
|
98
|
+
pass `{userId}` as the second argument to direct `sendMail` / `sendSms` calls so they honor
|
|
99
|
+
preferences; `sendPushToUser` already supplies its user id.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
await getCommsService().sendMail(message, {userId});
|
|
103
|
+
await getCommsService().sendSms(message, {userId});
|
|
104
|
+
```
|
|
105
|
+
|
|
73
106
|
Each send stores one `CommsMessage` row with `attempts[]`. Rendered payloads are retained
|
|
74
107
|
for `retainPayloadDays` (default 30, `0` disables) after `redactPayload`. Mail payloads
|
|
75
108
|
keep `to`, `from`, `subject`, `text`, `html`, `replyTo`, `templateId`, and
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# create-terreno-app
|
|
2
|
+
|
|
3
|
+
Unscoped npm CLI that writes a deployable Terreno backend + Expo frontend to disk.
|
|
4
|
+
|
|
5
|
+
Operator walkthrough (Mongo replica set, seed, SDK, web, deploy): [Create a Terreno app](../how-to/create-a-terreno-app.md). First-run tutorial: [Getting started](../tutorials/getting-started.md).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx create-terreno-app my-app --display-name "My App"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
npm (note the `--` before flags):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm create terreno-app my-app -- --display-name "My App"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The published package name is `create-terreno-app`. Generated `@terreno/*`
|
|
20
|
+
dependencies pin `^<create-terreno-app version>`.
|
|
21
|
+
|
|
22
|
+
## CLI
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bunx create-terreno-app <appName> --display-name <string> [--description <string>] [--mcp-server-url <url>] [--yes]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Flag | Required | Default |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `appName` (positional) | yes | kebab-case (`^[a-z][a-z0-9-]*$`) |
|
|
31
|
+
| `--display-name` | unless `--yes` | derived from `appName` when `--yes` |
|
|
32
|
+
| `--description` | no | empty |
|
|
33
|
+
| `--mcp-server-url` | no | hosted Terreno MCP URL |
|
|
34
|
+
| `--yes` | no | off |
|
|
35
|
+
|
|
36
|
+
Writes `<cwd>/<appName>/`. Refuses a non-empty target except `.git` / `.gitignore`. Does not run `bun install`, Mongo, or seed.
|
|
37
|
+
|
|
38
|
+
Printed next steps install, seed, then start the backend. SDK and web run in a second terminal while the backend is up.
|
|
39
|
+
|
|
40
|
+
The generated Profile tab uses `@terreno/ui` `TapToEdit` with `PATCH /auth/me`. Fonts come from `@terreno/ui`. `app.json` leaves icon/splash/favicon unset. `metro.config.js` pins `jspdf` so Metro can bundle admin PDF export. `tsconfig.json` has no `baseUrl` (TypeScript 6). Auth routes use `<Stack.Protected>`.
|
|
41
|
+
|
|
42
|
+
## Library
|
|
43
|
+
|
|
44
|
+
`generateAllFiles` returns the file list; `writeScaffold` writes it to disk. The CLI is the only supported way to scaffold an app. MCP `terreno_bootstrap_ai_rules` can add editor rules after the CLI runs.
|
|
@@ -69,6 +69,8 @@ Comprehensive guide to environment variables used across Terreno packages and ex
|
|
|
69
69
|
| `FRONTEND_URL` | example-backend CORS and `authOptions.publicAppUrl` | ❌ | `http://localhost:8082` | No | server |
|
|
70
70
|
| `API_URL` | microservice split | ❌ | — | No | server |
|
|
71
71
|
| `DISABLE_LOG_ALL_REQUESTS` | `@terreno/api` logging | ❌ | — | No | server |
|
|
72
|
+
| `ALLOW_MIGRATIONS` | `@terreno/api` `terreno-migrate` | ❌ | unset | No | server |
|
|
73
|
+
| `MIGRATIONS_DIR` | example-backend Dockerfile / `resolveMigrationDir` | ❌ | `<cwd>/migrations` when `dir` is `$bunfs` | No | server |
|
|
72
74
|
|
|
73
75
|
## Client / build-time
|
|
74
76
|
|
|
@@ -237,6 +239,24 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
|
|
|
237
239
|
| `TERRENO_MCP_EVAL` | MCP eval | ❌ | — | No | tooling |
|
|
238
240
|
| `TERRENO_PROJECT_ROOT` | MCP local | ❌ | — | No | tooling |
|
|
239
241
|
|
|
242
|
+
## Background jobs
|
|
243
|
+
|
|
244
|
+
| Variable | Read by | Required | Default | Secret | Scope |
|
|
245
|
+
|----------|---------|----------|---------|--------|-------|
|
|
246
|
+
| `JOBS_START_WORKER` | example-backend (`jobsStartWorker.ts`) | ❌ | `true` (unset) | No | server |
|
|
247
|
+
|
|
248
|
+
`@terreno/jobs` itself does **not** read environment variables — pass `JobsApp` and runner
|
|
249
|
+
options explicitly. `JOBS_START_WORKER` only gates whether the example API process calls
|
|
250
|
+
`jobsApp.startWorker()` after listen. Set `false` when running the standalone
|
|
251
|
+
`bun run jobs:worker` process against the same MongoDB.
|
|
252
|
+
|
|
253
|
+
`JOB_TRACE_LOGS` is an optional app convention for verbose worker logging (see
|
|
254
|
+
[API logging & tracing](api.md#logging--tracing)); not read by the jobs package.
|
|
255
|
+
|
|
256
|
+
Legacy names `GCP_TASKS_NOTIFICATIONS_QUEUE` and `GCP_TASK_PROCESSOR_QUEUE` appear in
|
|
257
|
+
example-backend **test** setup only. They are not read by `@terreno/jobs`. Use
|
|
258
|
+
`GcpCloudTasksRunner` constructor config instead. See [Jobs reference](jobs.md).
|
|
259
|
+
|
|
240
260
|
## Example backend (app-specific)
|
|
241
261
|
|
|
242
262
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
@@ -244,6 +264,14 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
|
|
|
244
264
|
| `OTEL_SERVICE_NAME` | OpenTelemetry (example-backend) | ❌ | `example-backend` | No | server |
|
|
245
265
|
| `PR_NUMBER` | PR preview deploy | ❌ | — | No | server |
|
|
246
266
|
| `PR_SERVICE_URL` | PR preview deploy | ❌ | — | No | server |
|
|
267
|
+
| `JOBS_RUNNER` | example-backend | ❌ | `mongo` | No | server |
|
|
268
|
+
| `JOBS_START_WORKER` | example-backend | ❌ | `true` with Mongo | No | server |
|
|
269
|
+
| `GCP_TASKS_PROJECT` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
|
|
270
|
+
| `GCP_TASKS_LOCATION` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
|
|
271
|
+
| `GCP_TASKS_QUEUE` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
|
|
272
|
+
| `GCP_TASKS_PUBLIC_URL` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
|
|
273
|
+
| `GCP_TASKS_OIDC_AUDIENCE` | example-backend Cloud Tasks runner | ❌ | `{publicUrl}/jobs/execute`; deployed Cloud Run uses the canonical tasks-service root URL so tagged PR callbacks pass platform IAM | No | server |
|
|
274
|
+
| `GCP_TASKS_SERVICE_ACCOUNT_EMAIL` | example-backend Cloud Tasks runner | ✅ when `JOBS_RUNNER=gcp-cloud-tasks` | — | No | server |
|
|
247
275
|
| `DEFAULT_PAGE_SIZE` | Configuration model | ❌ | `20` | No | server |
|
|
248
276
|
| `CRON_SECRET_KEY` | tests | ❌ | — | Yes | tooling |
|
|
249
277
|
| `WIDGET_CLIENT_SECRET` | widgets | ❌ | — | Yes | server |
|
|
@@ -278,3 +306,4 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
|
|
|
278
306
|
- [Build for web](../how-to/build-for-web.md)
|
|
279
307
|
- [Configure Better Auth](../how-to/configure-better-auth.md)
|
|
280
308
|
- [Rate limiting](../how-to/rate-limiting.md)
|
|
309
|
+
- [Durable background jobs](../how-to/background-jobs.md)
|