@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
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Durable background jobs
|
|
2
|
+
|
|
3
|
+
Use `@terreno/jobs` when work must survive process restarts, retry with backoff, run on a
|
|
4
|
+
schedule, or dispatch through Cloud Tasks / Vercel Queues. Register `JobsApp`, define
|
|
5
|
+
handlers, enqueue rows in MongoDB, then start a worker explicitly.
|
|
6
|
+
|
|
7
|
+
For one-off **CLI** scripts or in-process timers, keep `@terreno/api` [`wrapScript` / `cronjob`](../reference/api.md#wrapscript) — they are process-local. Admin panel **HTTP** script runs go through `admin/script` when `JobsApp` is registered (see below). See [Jobs reference](../reference/jobs.md) for exports, routes, and defaults.
|
|
8
|
+
|
|
9
|
+
## Install and register
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @terreno/jobs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer dependency: `mongoose ^8.0.0 || ^9.0.0`.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import {TerrenoApp} from "@terreno/api";
|
|
19
|
+
import {JobsApp, MongoJobRunner} from "@terreno/jobs";
|
|
20
|
+
|
|
21
|
+
const jobsApp = new JobsApp({
|
|
22
|
+
accessControl, // optional — requires admin:access AND admin:jobs when set
|
|
23
|
+
runner: new MongoJobRunner(),
|
|
24
|
+
timezone: "UTC",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
jobsApp.define("billing/capture", {
|
|
28
|
+
handler: async (payload, ctx) => {
|
|
29
|
+
ctx.log.info("capturing", {invoiceId: (payload as {invoiceId?: string}).invoiceId});
|
|
30
|
+
// business logic
|
|
31
|
+
},
|
|
32
|
+
retry: {maxAttempts: 5, backoffMs: 1_000, maxBackoffMs: 900_000},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
new TerrenoApp({userModel: User})
|
|
36
|
+
.register(jobsApp)
|
|
37
|
+
.start();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`JobsApp.register()` wires admin routes and optional `POST /jobs/execute`. It does **not**
|
|
41
|
+
start a worker. Call `jobsApp.startWorker()` from the API process or a dedicated worker
|
|
42
|
+
entrypoint.
|
|
43
|
+
|
|
44
|
+
## Admin scripts
|
|
45
|
+
|
|
46
|
+
When `JobsApp` is registered **before** `AdminApp`, `POST {adminBase}/scripts/:name/run`
|
|
47
|
+
enqueues job `admin/script` (`maxAttempts: 1`) and still returns `{taskId}` for the existing
|
|
48
|
+
Scripts poll UI (`BackgroundTask`). Progress, logs, dry/wet, and cancel stay on that row.
|
|
49
|
+
Cancel also cancels the job so `ctx.signal` aborts.
|
|
50
|
+
|
|
51
|
+
Apps without `JobsApp` keep the previous in-process `void` runner. The CLI
|
|
52
|
+
(`runScriptCli` / `bun run script`) stays process-local.
|
|
53
|
+
|
|
54
|
+
Register the same script catalog on the worker:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import {defineAdminScriptJob} from "@terreno/admin-backend";
|
|
58
|
+
|
|
59
|
+
defineAdminScriptJob(jobsApp, (name) => adminScripts.find((script) => script.name === name));
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`AdminApp` also binds `admin/script` from its `scripts` list at register time. Cloud Tasks
|
|
63
|
+
uses this path automatically when `JobsApp` is constructed with `GcpCloudTasksRunner` — the
|
|
64
|
+
worker/execute process must load the same script definitions. If runner `enqueue` throws,
|
|
65
|
+
the HTTP handler marks the `BackgroundTask` failed and returns 500 instead of leaving it
|
|
66
|
+
`pending`.
|
|
67
|
+
|
|
68
|
+
## Enqueue
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {getJobsService} from "@terreno/jobs";
|
|
72
|
+
import {DateTime} from "luxon";
|
|
73
|
+
|
|
74
|
+
await getJobsService().enqueue({
|
|
75
|
+
name: "billing/capture",
|
|
76
|
+
payload: {invoiceId: "inv_123"},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await getJobsService().enqueue({
|
|
80
|
+
name: "billing/capture",
|
|
81
|
+
payload: {invoiceId: "inv_123"},
|
|
82
|
+
idempotencyKey: "capture:inv_123",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await getJobsService().enqueue({
|
|
86
|
+
name: "billing/capture",
|
|
87
|
+
payload: {invoiceId: "inv_123"},
|
|
88
|
+
runAt: DateTime.utc().plus({hours: 1}).toJSDate(),
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Unknown `name` → `APIError` 400.
|
|
93
|
+
- Duplicate `(name, idempotencyKey)` returns the existing row and does **not** redispatch.
|
|
94
|
+
- `enqueue` persists first; the injected `JobRunner.enqueue` runs only for **new** rows.
|
|
95
|
+
|
|
96
|
+
## In-process worker vs standalone worker
|
|
97
|
+
|
|
98
|
+
| Mode | When | Steps |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| In-process | Local dev, single replica | After `TerrenoApp.start()`, call `jobsApp.startWorker()`. |
|
|
101
|
+
| Standalone | Production API + worker split | API: `JOBS_START_WORKER=false`. Run `bun run jobs:worker` (example-backend) in a second process with the same job definitions and Mongo URI. |
|
|
102
|
+
|
|
103
|
+
Example-backend gates the API-process worker with `JOBS_START_WORKER` (unset → `true`):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# API only — no poll loop in the web process
|
|
107
|
+
JOBS_START_WORKER=false bun run backend:dev
|
|
108
|
+
|
|
109
|
+
# Dedicated worker (same env + Mongo as API)
|
|
110
|
+
bun run jobs:worker
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
On shutdown, call `jobsApp.stopWorker()` so in-flight handlers receive `ctx.signal` abort.
|
|
114
|
+
|
|
115
|
+
## Mongo runner: restart, retry, idempotency, schedules
|
|
116
|
+
|
|
117
|
+
**Restart:** Jobs are Mongo documents. A crashed worker leaves `running` rows; after
|
|
118
|
+
`lockTtlMs` (default 15 minutes) another worker reclaims them via `lockedAt` expiry.
|
|
119
|
+
|
|
120
|
+
**Retry:** Handler throws → `attemptCount++`, `attempts[]` entry, exponential backoff with
|
|
121
|
+
equal jitter, `status: "pending"` and future `runAt`. At `maxAttempts` (default 5) →
|
|
122
|
+
`status: "dead"`. Missing handler → `status: "failed"` (no automatic retry).
|
|
123
|
+
|
|
124
|
+
**Idempotency:** Optional `idempotencyKey` per `name` with a partial unique index. Use for
|
|
125
|
+
“enqueue once” semantics, not handler-side dedupe.
|
|
126
|
+
|
|
127
|
+
**Schedules:** Pass `schedule: {cron, timezone?}` on `define`. Worker `startWorker()` calls
|
|
128
|
+
`reconcileSchedules()`; the poll loop ticks due schedules, enqueues a child job, and skips a
|
|
129
|
+
tick when a child for that schedule is still `pending` / `running` / `scheduled`.
|
|
130
|
+
|
|
131
|
+
## At-least-once handlers
|
|
132
|
+
|
|
133
|
+
Delivery is **at-least-once**: lock expiry, Cloud Task retries, or Vercel queue redelivery
|
|
134
|
+
can run a handler more than once.
|
|
135
|
+
|
|
136
|
+
- Make handlers idempotent (stable keys, upserts, “already processed” checks).
|
|
137
|
+
- Use `idempotencyKey` on enqueue to prevent duplicate **rows**, not duplicate execution after admin retry/requeue.
|
|
138
|
+
- Respect `ctx.signal` for cooperative cancellation.
|
|
139
|
+
- Use `ctx.log` (scoped logger) — `jobId` is in request context for log correlation.
|
|
140
|
+
|
|
141
|
+
## Admin
|
|
142
|
+
|
|
143
|
+
`JobsApp.adminContribution()` registers:
|
|
144
|
+
|
|
145
|
+
- Custom screen `jobs` (dashboard)
|
|
146
|
+
- Home widget `jobs` (dead / running counts)
|
|
147
|
+
|
|
148
|
+
When `accessControl` is configured, callers need **both** `admin:access` and `admin:jobs`
|
|
149
|
+
(default `terrenoStatements.admin` includes both). When `accessControl` is omitted, routes
|
|
150
|
+
use legacy `user.admin === true`.
|
|
151
|
+
|
|
152
|
+
Frontend routes (example): `/admin/jobs`, `/admin/jobs/[id]`. Widgets ship in
|
|
153
|
+
`@terreno/admin-frontend` (`JOBS_ADMIN_WIDGETS`, `JOBS_HOME_WIDGETS`) — no host remap.
|
|
154
|
+
See [Import pre-built admins](admin-import-prebuilt.md).
|
|
155
|
+
|
|
156
|
+
Admin payloads are **hidden by default** (omit `redactPayload`). Opt in with a projection —
|
|
157
|
+
do not pass through the raw payload unless every field is safe for operators:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Default — no hook; payload omitted from admin API responses
|
|
161
|
+
new JobsApp({accessControl});
|
|
162
|
+
|
|
163
|
+
// Opt-in — expose only non-sensitive fields
|
|
164
|
+
new JobsApp({
|
|
165
|
+
accessControl,
|
|
166
|
+
redactPayload: ({name, payload}) => {
|
|
167
|
+
if (name === "billing/capture") {
|
|
168
|
+
return {invoiceId: (payload as {invoiceId?: string}).invoiceId};
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## GCP Cloud Tasks runner
|
|
176
|
+
|
|
177
|
+
Optional peer: `@google-cloud/tasks`.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import {GcpCloudTasksRunner} from "@terreno/jobs/runners/gcpCloudTasks";
|
|
181
|
+
|
|
182
|
+
const jobsApp = new JobsApp({
|
|
183
|
+
executeAuth: async (req) => {
|
|
184
|
+
// Verify OIDC token on POST /jobs/execute — your responsibility
|
|
185
|
+
return verifyGcpOidc(req);
|
|
186
|
+
},
|
|
187
|
+
runner: new GcpCloudTasksRunner({
|
|
188
|
+
project: "my-project",
|
|
189
|
+
location: "us-central1",
|
|
190
|
+
queue: "background-jobs",
|
|
191
|
+
publicUrl: "https://api.example.com",
|
|
192
|
+
serviceAccountEmail: "tasks-invoker@my-project.iam.gserviceaccount.com",
|
|
193
|
+
oidcAudience: "https://api.example.com/jobs/execute", // optional; defaults to execute URL
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`GcpCloudTasksRunner` sets `requiresExecuteRoute: true`. Each enqueue creates an HTTP POST
|
|
199
|
+
to `{publicUrl}/jobs/execute` with body `{jobId}` (base64) and an OIDC token. Delayed jobs
|
|
200
|
+
use Cloud Tasks `scheduleTime`. HTTP tasks set `dispatchDeadline` to `{seconds: 1800}`
|
|
201
|
+
(REST JSON `"1800s"`) by default so Cloud Tasks does not retry at 10 minutes while a
|
|
202
|
+
30-minute Cloud Run handler is still running. Override with `dispatchDeadlineSeconds`
|
|
203
|
+
(15–1800).
|
|
204
|
+
`@terreno/jobs` does **not** read `GCP_TASK_*` env vars —
|
|
205
|
+
pass explicit constructor config. Compiled `bun build --compile` binaries must pass a
|
|
206
|
+
`client` that does not load `@google-cloud/tasks` (that package reads
|
|
207
|
+
`cloud_tasks_client_config.json` from disk and is omitted from `$bunfs`). The example
|
|
208
|
+
backend POSTs to the Cloud Tasks REST API with `google-auth-library`.
|
|
209
|
+
|
|
210
|
+
The deployed example backend selects this runner with `JOBS_RUNNER=gcp-cloud-tasks`.
|
|
211
|
+
Infra Manager creates one queue, the API Cloud Run identity
|
|
212
|
+
`terreno-backend-runtime` (queue enqueuer + `actAs` on the callback SA), and a
|
|
213
|
+
callback-only `terreno-jobs-invoker` service account. The CD script supplies
|
|
214
|
+
the remaining `GCP_TASKS_*` values. Do not grant enqueue or `actAs` to the
|
|
215
|
+
project default Compute Engine SA. Both GitHub Actions and CircleCI deploy the same
|
|
216
|
+
runner configuration so either deploy path preserves Cloud Tasks execution. The API
|
|
217
|
+
process starts the worker so cron schedules
|
|
218
|
+
enqueue Cloud Tasks (`GcpCloudTasksRunner.start()` ticks Mongo schedules only). The tasks
|
|
219
|
+
Cloud Run service is the execution pool: Cloud Tasks pushes authenticated callbacks to it,
|
|
220
|
+
and queue rate limits bound concurrency. Keep `JOBS_START_WORKER=false` on the tasks
|
|
221
|
+
service so it does not double-tick schedules. Do not run `jobs:worker` in this mode —
|
|
222
|
+
that process would also tick schedules.
|
|
223
|
+
|
|
224
|
+
PR previews use the same queue but different callback URLs and databases. GitHub Actions
|
|
225
|
+
and CircleCI both deploy the `pr-<number>` tag on the tasks Cloud Run service before the
|
|
226
|
+
API preview starts enqueueing. GitHub Actions uses `env_vars_update_strategy: overwrite`
|
|
227
|
+
and `secrets_update_strategy: overwrite` (CircleCI `--set-env-vars`) so `MONGO_DB_NAME` /
|
|
228
|
+
`PR_NUMBER` and leftover secrets on a tagged revision do not merge into the next production
|
|
229
|
+
or preview deploy. The preview smoke container receives the same `JOBS_*` env as Cloud Run.
|
|
230
|
+
A task created by PR 123 targets that tag and reads
|
|
231
|
+
`terreno-example-pr-123`; it cannot execute against another PR or production.
|
|
232
|
+
|
|
233
|
+
## Vercel Queues runner
|
|
234
|
+
|
|
235
|
+
Optional peer: `@vercel/queue`.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import {
|
|
239
|
+
createVercelQueuesConsumer,
|
|
240
|
+
VercelQueuesRunner,
|
|
241
|
+
} from "@terreno/jobs/runners/vercelQueues";
|
|
242
|
+
|
|
243
|
+
const jobsApp = new JobsApp({
|
|
244
|
+
runner: new VercelQueuesRunner({topic: "terreno-jobs"}),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Private consumer route (platform auth via handleCallback — not Express execute route)
|
|
248
|
+
export const POST = createVercelQueuesConsumer({host: jobsApp});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`vercel.json` must declare the queue trigger (private route):
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"experimentalTriggers": [
|
|
256
|
+
{"type": "queue/v2beta", "topic": "terreno-jobs"}
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**7-day limit:** `delaySeconds` and retention are capped at 604_800 seconds (7 days). `runAt`
|
|
262
|
+
beyond that window throws `VercelQueueRunAtBeyondLimitError` at enqueue — use a shorter delay
|
|
263
|
+
or a different runner for longer deferrals. Retention is bumped to cover `delaySeconds` when
|
|
264
|
+
needed.
|
|
265
|
+
|
|
266
|
+
`VercelQueuesRunner` does **not** mount `POST /jobs/execute`; the consumer calls
|
|
267
|
+
`executeJobById` through `createVercelQueuesConsumer`.
|
|
268
|
+
|
|
269
|
+
## Custom runner
|
|
270
|
+
|
|
271
|
+
Implement `JobRunner`:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import type {JobRunner} from "@terreno/jobs";
|
|
275
|
+
|
|
276
|
+
const runner: JobRunner = {
|
|
277
|
+
id: "my-bus",
|
|
278
|
+
requiresExecuteRoute: false, // set true if you use POST /jobs/execute
|
|
279
|
+
async enqueue(job) {
|
|
280
|
+
await myBus.publish({jobId: job._id.toString()});
|
|
281
|
+
},
|
|
282
|
+
async start({jobs, signal, pollIntervalMs}) {
|
|
283
|
+
// optional — MongoJobRunner implements start; dispatch-only runners omit it
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Inject with `new JobsApp({runner})`. Dispatch failures on untouched `pending` rows roll back
|
|
289
|
+
the insert when possible (`JobDispatchError`).
|
|
290
|
+
|
|
291
|
+
## Related
|
|
292
|
+
|
|
293
|
+
- [Jobs reference](../reference/jobs.md)
|
|
294
|
+
- [Environment variables — jobs](../reference/environment-variables.md#background-jobs)
|
|
295
|
+
- [Import pre-built admins](admin-import-prebuilt.md)
|
|
296
|
+
- Implementation plan: [job-queues.md](https://github.com/FlourishHealth/terreno/blob/master/docs/implementationPlans/job-queues.md)
|
|
@@ -22,7 +22,18 @@ Embedded app (`example-frontend`):
|
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
24
|
// app/admin/_layout.tsx
|
|
25
|
-
|
|
25
|
+
const syncConflicts = useConflicts();
|
|
26
|
+
|
|
27
|
+
<AdminProvider
|
|
28
|
+
api={api}
|
|
29
|
+
apiBase="/admin"
|
|
30
|
+
apiOrigin={baseUrl}
|
|
31
|
+
getAuthHeaders={getAdminAuthHeaders}
|
|
32
|
+
routeBase="/admin"
|
|
33
|
+
syncConflicts={syncConflicts}
|
|
34
|
+
syncDb={adminSyncDb}
|
|
35
|
+
widgets={{screens: {"sync-lab": SyncLabScreen}}}
|
|
36
|
+
>
|
|
26
37
|
<AdminShellLayout
|
|
27
38
|
api={api}
|
|
28
39
|
apiBase="/admin"
|
|
@@ -36,11 +47,25 @@ Embedded app (`example-frontend`):
|
|
|
36
47
|
</AdminProvider>
|
|
37
48
|
```
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
Render this tree inside `SyncDbProvider client={adminSyncDb}`. If product screens
|
|
51
|
+
also sync the same collection, make `adminSyncDb` a dedicated client with a unique
|
|
52
|
+
store name and `windowCollections` set; do not mix admin rows into the owner/tenant
|
|
53
|
+
product store. Start that client after `canOpenAdminPage` succeeds, block the admin
|
|
54
|
+
shell until `start()` resolves, and call `stop()` when the admin layout unmounts.
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
Gate entry with `canOpenAdminPage` from `@terreno/rtk` (`admin:access`). Do not
|
|
57
|
+
rely on `user.admin` alone when RBAC is on. Import `baseUrl` from `@terreno/rtk`
|
|
58
|
+
and pass it as `apiOrigin` so admin RPC (`GET /admin/config`, `/rbac/roles`, …)
|
|
59
|
+
hits the API instead of the Expo web origin.
|
|
60
|
+
|
|
61
|
+
Standalone SPA (`admin-spa`): `routeBase=""`, `apiBase="/admin"`, `credentials="same-origin"`,
|
|
62
|
+
and `getAuthHeaders` that return `{}` so the cookie session is sent without a Bearer header.
|
|
63
|
+
Omit `apiOrigin`. Each route wraps `AdminShellLayout` because the root layout is only
|
|
64
|
+
providers. Model tables render **Create** once, in the table chrome
|
|
65
|
+
(`testID="admin-create-button"`), because admin Expo stacks use `headerShown: false`.
|
|
66
|
+
Model forms render **Save** / **Delete** in the form chrome
|
|
67
|
+
(`testID="admin-save-button"` / `admin-delete-button`) for the same reason.
|
|
68
|
+
The navigator header only receives the screen title.
|
|
44
69
|
|
|
45
70
|
## 3. Keep generic model routes generic
|
|
46
71
|
|
|
@@ -53,9 +78,11 @@ app/admin/
|
|
|
53
78
|
[model]/[id].tsx # <AdminModelForm />
|
|
54
79
|
```
|
|
55
80
|
|
|
56
|
-
`AdminScreenRouter` resolves the URL segment as: `__scripts` → script list,
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
`AdminScreenRouter` resolves the URL segment as: `__scripts` → script list,
|
|
82
|
+
`__migrations` → migrations status (when `GET /admin/config` has
|
|
83
|
+
`migrations.enabled`), matching model name → table, matching custom-screen
|
|
84
|
+
name → `widgets.screens[name]`, else not-found. Do not copy `AdminModelTable`
|
|
85
|
+
into a new file per model.
|
|
59
86
|
|
|
60
87
|
Create paths are `[model]/create`, not `new`.
|
|
61
88
|
|
|
@@ -96,10 +123,11 @@ for those paths. Otherwise `[model]/[id]` treats the id as a generic document.
|
|
|
96
123
|
|
|
97
124
|
## 5. Fetch data the admin way
|
|
98
125
|
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
126
|
+
- String-`_id` collections configured with `adminBroadcast` use windowed syncdb
|
|
127
|
+
when the host passes `syncDb` plus fetch auth.
|
|
128
|
+
- ObjectId model CRUD and API-only hosts keep `useAdminApi`.
|
|
129
|
+
- Bulk patch remains server-side: native admin fetch for windowed hosts, RTK fallback otherwise.
|
|
130
|
+
- Admin RPC uses the host-bound native fetch client. Do not add axios.
|
|
103
131
|
|
|
104
132
|
## 6. Sidebar and grouping
|
|
105
133
|
|
|
@@ -108,7 +136,8 @@ Do not add sidebar links in app code. The shell reads `/admin/config`.
|
|
|
108
136
|
|
|
109
137
|
Platform links appear only when `platformTools` says they should. Pass
|
|
110
138
|
`configurationPath`, `rolesPath`, and `versionConfigPath` into `AdminShellLayout`
|
|
111
|
-
so those buttons have destinations.
|
|
139
|
+
so those buttons have destinations. Jobs (`customScreens` name `jobs`) appears
|
|
140
|
+
under Platform, not Screens.
|
|
112
141
|
|
|
113
142
|
## 7. Verify
|
|
114
143
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Compose charts in a dashboard grid
|
|
2
|
+
|
|
3
|
+
Put each chart in a `Card`, then wrap the cards in `DashboardGrid`.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import type {FC} from "react";
|
|
7
|
+
import {BarChart, Card, DashboardGrid, LineChart} from "@terreno/ui";
|
|
8
|
+
|
|
9
|
+
const POINTS = [
|
|
10
|
+
{label: "Mon", value: 3},
|
|
11
|
+
{label: "Tue", value: 5},
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export const OpsDashboard: FC = () => {
|
|
15
|
+
return (
|
|
16
|
+
<DashboardGrid>
|
|
17
|
+
<Card>
|
|
18
|
+
<LineChart data={POINTS} legendLabel="Signups" />
|
|
19
|
+
</Card>
|
|
20
|
+
<Card>
|
|
21
|
+
<BarChart data={POINTS} legendLabel="Sessions" />
|
|
22
|
+
</Card>
|
|
23
|
+
</DashboardGrid>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
1. Pass a single series of `{label, value}` points.
|
|
29
|
+
2. Use `emptyText` and `loading` on the chart, not a custom overlay.
|
|
30
|
+
3. Press or hover a mark for `{label}: {value}`. Zero and negative bars stay pressable; bars grow from the zero baseline.
|
|
31
|
+
4. Keep `DashboardGrid` eager; import charts from `@terreno/ui/LineChart` when cold start matters. `gap` is subtracted from cell width so the breakpoint column count still fits.
|
|
32
|
+
|
|
33
|
+
`DonutChart` ignores `legendLabel` and draws one legend row per slice. Override a slice with `color` on that point.
|
|
34
|
+
|
|
35
|
+
Charts have no width prop: each one fills the container you put it in and redraws when that width changes, so size the `Card` or `Box` around it. `height` is the chart's whole height, tick row and tooltip row included, so `height={140}` fits a 140px slot; the tooltip row is always reserved so hovering never reflows the page. Long x labels truncate inside their band rather than widening the chart.
|
|
36
|
+
|
|
37
|
+
Prove paint with `bun run ui:charts:compare` against `demo/rendered-snapshots/`, not with JSON snapshots. See [Compare rendered chart snapshots](compare-chart-rendered-snapshots.md).
|