@aotter/mantle 0.1.0-alpha.9 → 0.1.2-alpha.2

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.
Files changed (109) hide show
  1. package/README.md +38 -38
  2. package/dist/cli/generate.d.ts +10 -1
  3. package/dist/cli/generate.d.ts.map +1 -1
  4. package/dist/cli/generate.js +49 -4
  5. package/dist/cli/generate.js.map +1 -1
  6. package/dist/cli/main.js +0 -8
  7. package/dist/cli/main.js.map +1 -1
  8. package/dist/codegen/emitMantleModule.js +8 -0
  9. package/dist/codegen/emitMantleModule.js.map +1 -1
  10. package/docs/adapter-guide.md +23 -26
  11. package/docs/adr/0008-structured-diagnostic-shape.md +7 -2
  12. package/docs/adr/0010-locale-and-translates.md +14 -16
  13. package/docs/adr/0011-adapter-port-spec.md +2 -1
  14. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +100 -0
  15. package/docs/adr/0018-core-starters-repository-boundary.md +3 -0
  16. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +38 -2
  17. package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +199 -0
  18. package/docs/adr/0021-retire-starter-scaffolding.md +30 -0
  19. package/docs/adr/0022-caller-observed-version-occ.md +212 -0
  20. package/docs/adr/0023-port-failure-contract.md +69 -0
  21. package/docs/adr/0024-manifest-native-schema-tables.md +260 -0
  22. package/docs/adr/README.md +19 -3
  23. package/docs/adr/adr-lite-803-request-diagnostics.md +38 -0
  24. package/docs/adr/adr-lite-808-route-readiness.md +47 -0
  25. package/docs/adr/adr-lite-809-bounded-public-content.md +71 -0
  26. package/docs/adr/adr-lite-812-native-parity.md +150 -0
  27. package/docs/adr/adr-lite-823-home-statistics.md +64 -0
  28. package/docs/adr/adr-lite-842-mcp-authoring.md +30 -0
  29. package/docs/adr/adr-lite-861-admin-webmcp.md +42 -0
  30. package/docs/adr/adr-lite-909-admin-ui-kit.md +37 -0
  31. package/docs/api-mcp-authorization.md +3 -664
  32. package/docs/auth-hosting-model.md +4 -4
  33. package/docs/cloudflare-low-level-composition.md +3 -104
  34. package/docs/deferred-lifecycle-queues.md +2 -243
  35. package/docs/design-atoms.md +16 -796
  36. package/docs/direct-authoring.md +5 -0
  37. package/docs/examples/minimal-worker/README.md +37 -0
  38. package/docs/examples/minimal-worker/manifests/site.yaml +25 -0
  39. package/docs/examples/minimal-worker/package.json +26 -0
  40. package/docs/examples/minimal-worker/smoke.mjs +23 -0
  41. package/docs/examples/minimal-worker/src/index.ts +4 -0
  42. package/docs/examples/minimal-worker/tsconfig.json +17 -0
  43. package/docs/examples/minimal-worker/wrangler.jsonc +22 -0
  44. package/docs/handbook/cloudflare/authentication.md +167 -0
  45. package/docs/handbook/cloudflare/bindings.md +236 -0
  46. package/docs/handbook/cloudflare/conventional-worker.md +162 -0
  47. package/docs/handbook/cloudflare/deferred-hooks-queues.md +171 -0
  48. package/docs/handbook/cloudflare/deploy-and-operate.md +105 -0
  49. package/docs/handbook/cloudflare/low-level-composition.md +169 -0
  50. package/docs/handbook/cloudflare/media-r2.md +216 -0
  51. package/docs/handbook/cloudflare/public-web.md +144 -0
  52. package/docs/handbook/concepts/authorization.md +116 -0
  53. package/docs/handbook/concepts/four-atoms.md +173 -0
  54. package/docs/handbook/concepts/lifecycle-and-locales.md +142 -0
  55. package/docs/handbook/concepts/mcp-and-agents.md +109 -0
  56. package/docs/handbook/concepts/procedures-and-triggers.md +221 -0
  57. package/docs/handbook/concepts/runtime-and-adapters.md +177 -0
  58. package/docs/handbook/concepts/views.md +191 -0
  59. package/docs/handbook/examples/commerce-transaction.md +813 -0
  60. package/docs/handbook/examples/guarded-api.md +427 -0
  61. package/docs/handbook/examples/intake-form.md +319 -0
  62. package/docs/handbook/examples/legal-documents.md +218 -0
  63. package/docs/handbook/examples/procurement-approvals.md +240 -0
  64. package/docs/handbook/examples/publication.md +240 -0
  65. package/docs/handbook/examples/reservation.md +220 -0
  66. package/docs/handbook/navigation.json +63 -0
  67. package/docs/handbook/reference/authorization.md +295 -0
  68. package/docs/handbook/reference/diagnostics.md +183 -0
  69. package/docs/handbook/reference/manifest.md +132 -0
  70. package/docs/handbook/reference/procedure.md +263 -0
  71. package/docs/handbook/reference/schema.md +250 -0
  72. package/docs/handbook/reference/site-config.md +233 -0
  73. package/docs/handbook/reference/surface.md +207 -0
  74. package/docs/handbook/reference/trigger.md +193 -0
  75. package/docs/handbook/reference/view.md +256 -0
  76. package/docs/handbook/start/project-and-cli.md +122 -0
  77. package/docs/handbook/start/quickstart-worker.md +193 -0
  78. package/docs/labels.md +3 -1
  79. package/docs/media-uploads.md +3 -184
  80. package/docs/migration-0.1.2.md +60 -3
  81. package/docs/performance-harness.md +127 -10
  82. package/docs/release-process.md +85 -231
  83. package/docs/schema-indexes.md +3 -180
  84. package/docs/sealed-pipeline-ownership.md +6 -5
  85. package/docs/spec-only-host-adoption.md +158 -0
  86. package/docs/transaction-patterns.md +5 -0
  87. package/package.json +21 -25
  88. package/skills/README.md +4 -4
  89. package/skills/develop/SKILL.md +23 -28
  90. package/skills/install/SKILL.md +55 -147
  91. package/skills/provision/SKILL.md +4 -3
  92. package/skills/theme/SKILL.md +6 -6
  93. package/skills/update/SKILL.md +32 -68
  94. package/dist/cli/create.d.ts +0 -2
  95. package/dist/cli/create.d.ts.map +0 -1
  96. package/dist/cli/create.js +0 -243
  97. package/dist/cli/create.js.map +0 -1
  98. package/dist/cli/update.d.ts +0 -2
  99. package/dist/cli/update.d.ts.map +0 -1
  100. package/dist/cli/update.js +0 -413
  101. package/dist/cli/update.js.map +0 -1
  102. package/dist/provision/renderProvisionBundle.d.ts +0 -70
  103. package/dist/provision/renderProvisionBundle.d.ts.map +0 -1
  104. package/dist/provision/renderProvisionBundle.js +0 -367
  105. package/dist/provision/renderProvisionBundle.js.map +0 -1
  106. package/dist/provision.d.ts +0 -2
  107. package/dist/provision.d.ts.map +0 -1
  108. package/dist/provision.js +0 -2
  109. package/dist/provision.js.map +0 -1
@@ -1,106 +1,5 @@
1
- # Low-level Cloudflare Worker composition
1
+ # Cloudflare low-level composition
2
2
 
3
- Use `createMantleWorker` unless the application must own the top-level Worker
4
- assembly. This copyable fixture keeps Mantle's standard bindings, Auth,
5
- Admin/API routes, OAuth/MCP dispatch, cache policy and redacted error boundary,
6
- while adding one application-owned post-response Queue audit across every route.
3
+ This guide moved into the handbook: [`docs/handbook/cloudflare/low-level-composition.md`](handbook/cloudflare/low-level-composition.md).
7
4
 
8
- ```ts
9
- import { Hono } from "hono";
10
- import {
11
- applyCachePolicy,
12
- conventionalMcpResource,
13
- createMantleRuntimeRef,
14
- createConventionalAuth,
15
- createConventionalBindings,
16
- createMcpApiHandler,
17
- mountAuthorize,
18
- mountAdmin,
19
- mountRuntimeEndpoints,
20
- runMantleWorkerRequest,
21
- setupIncompleteAuthResponse,
22
- type MantleCloudflareEnv,
23
- } from "@aotter/mantle/cloudflare";
24
- import { plan } from "../.mantle/generated/mantle.js";
25
-
26
- interface Env extends MantleCloudflareEnv {
27
- readonly AUDIT_QUEUE: Queue<{
28
- readonly kind: "request-complete";
29
- readonly path: string;
30
- readonly status: number;
31
- }>;
32
- }
33
-
34
- let assembled: ReturnType<typeof assemble> | undefined;
35
-
36
- export default {
37
- fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
38
- return runMantleWorkerRequest(async () => {
39
- assembled ??= assemble(env);
40
- const incomplete = await setupIncompleteAuthResponse(request, assembled.auth);
41
- const response = incomplete ?? await assembled.fetch(request, env, ctx);
42
- ctx.waitUntil(env.AUDIT_QUEUE.send({
43
- kind: "request-complete",
44
- path: new URL(request.url).pathname,
45
- status: response.status,
46
- }));
47
- return response;
48
- });
49
- },
50
- } satisfies ExportedHandler<Env>;
51
-
52
- function assemble(env: Env) {
53
- const bindings = createConventionalBindings(env);
54
- const auth = createConventionalAuth(env);
55
- const ref = createMantleRuntimeRef({ plan, bindings, auth });
56
- const app = new Hono<{ Bindings: Env }>();
57
-
58
- mountRuntimeEndpoints(app, ref);
59
- if (bindings.adminAssets) mountAdmin(app, ref, bindings.adminAssets);
60
- mountAuthorize(app, { auth });
61
- app.get("/cache-probe", () => new Response("public", {
62
- headers: { "cache-control": "public, s-maxage=60" },
63
- }));
64
-
65
- const resource = conventionalMcpResource(env);
66
- const mcp = new Map([
67
- ["/mcp/staff", createMcpApiHandler<Env>({ ref, surface: "staff", resource })],
68
- ["/mcp", createMcpApiHandler<Env>({ ref, surface: "public", resource })],
69
- ]);
70
- return {
71
- auth,
72
- async fetch(request: Request, workerEnv: Env, ctx: ExecutionContext) {
73
- // Low-level owners must prepare the canonical D1 schema before Better
74
- // Auth handles a token, client, consent, or CIMD request.
75
- await ref.get();
76
- const handler = mcp.get(new URL(request.url).pathname);
77
- const response = handler?.fetch
78
- ? await handler.fetch(request, workerEnv, ctx)
79
- : await app.fetch(request, workerEnv, ctx);
80
- return applyCachePolicy(request, response);
81
- },
82
- };
83
- }
84
- ```
85
-
86
- Keep the conventional `DB` binding and `nodejs_compat`. CIMD metadata fetches
87
- also require `global_fetch_strictly_public`; add the Queue producer in
88
- `wrangler.jsonc`:
89
-
90
- ```jsonc
91
- {
92
- "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
93
- "queues": {
94
- "producers": [
95
- { "binding": "AUDIT_QUEUE", "queue": "my-site-audit" }
96
- ]
97
- }
98
- }
99
- ```
100
-
101
- After copying, the Worker entry, request audit, custom route and Queue contract
102
- belong to the application. Mantle still owns the imported adapters and
103
- standard route behavior; update them through the package version. Do not copy
104
- their source or replace Auth, MCP or cache handling locally. A copied
105
- composition has no automatic merge path back to `createMantleWorker`; keep it
106
- only while the custom top-level lifecycle remains necessary.
5
+ The handbook ships inside the `@aotter/mantle` package under `node_modules/@aotter/mantle/docs/handbook/` and describes the installed release.
@@ -1,246 +1,5 @@
1
1
  # Deferred lifecycle hooks on Cloudflare Queues
2
2
 
3
- Mantle can opt `after_*` lifecycle Triggers into a Cloudflare Queue. This is
4
- useful for notifications and projections that should not add request latency,
5
- but it is not an exactly-once transaction boundary.
3
+ This guide moved into the handbook: [`docs/handbook/cloudflare/deferred-hooks-queues.md`](handbook/cloudflare/deferred-hooks-queues.md).
6
4
 
7
- ## Contract
8
-
9
- - The entry write commits before Queue delivery. D1 and Queue do not share a
10
- transaction, so a Worker failure can still occur in that gap.
11
- - A resolved `Queue.send()` means Cloudflare accepted the message. Consumption
12
- is at-least-once; a message can run more than once.
13
- - A rejected or ambiguous send falls back with the same event identity through
14
- `ctx.waitUntil`, or inline when `waitUntil` is unavailable. `waitUntil` is
15
- best-effort and does not close the D1-to-Queue gap.
16
- - A consumer acknowledges a message only after every captured Trigger
17
- succeeds. It runs all captured Triggers before surfacing failures, so a later
18
- failure can replay an earlier success.
19
- - Malformed, unsupported, removed-Trigger, and persistently failing messages
20
- are retried and then sent to the configured dead-letter queue (DLQ). Without
21
- a DLQ, Cloudflare discards them after `max_retries`.
22
-
23
- Deferred handlers must therefore be idempotent. Mantle does not promise
24
- exactly-once execution.
25
-
26
- ## Version 1 event
27
-
28
- The strict v1 envelope contains:
29
-
30
- ```ts
31
- interface DeferredHookEnvelope {
32
- version: 1;
33
- eventId: string;
34
- triggerNames: readonly string[];
35
- hook: "after_create" | "after_update" | "after_delete" | "after_publish";
36
- schema: string;
37
- entry: EntryRow;
38
- ctxSnapshot: CtxSnapshot | null;
39
- }
40
- ```
41
-
42
- `triggerNames` is captured when the entry mutation runs. A later deployment
43
- does not add a new Trigger to an old event. Removing or renaming a captured
44
- Trigger makes that old message fail validation at dispatch and eventually
45
- reach the DLQ instead of silently changing its meaning.
46
-
47
- Each handler receives persisted `entry.data` as its input and:
48
-
49
- ```ts
50
- ctx.event = {
51
- id: envelope.eventId,
52
- trigger: currentTriggerName,
53
- hook: envelope.hook,
54
- schema: envelope.schema,
55
- entry: envelope.entry,
56
- };
57
- ```
58
-
59
- Use `${ctx.event.id}:${ctx.event.trigger}` as the handler's idempotency key.
60
- The same key survives enqueue fallback, Queue retries, and replay. Synchronous
61
- `before_*` hooks may still receive pre-projection request input such as a
62
- CAPTCHA token; deferred `after_*` envelopes never retain that input. The
63
- identity snapshot contains normalized actor and credential metadata, never raw
64
- cookies, tokens, or API keys.
65
-
66
- Cloudflare's 128 KB limit is decimal and includes platform metadata. The
67
- adapter therefore rejects non-JSON envelopes and encoded envelopes at or above
68
- 127,000 bytes before calling `Queue.send`, leaving at least 1 KB of headroom.
69
- Keep entry payloads smaller than that supported limit.
70
-
71
- ## Cloudflare opt-in
72
-
73
- Create the internal queue and its DLQ:
74
-
75
- ```sh
76
- pnpm wrangler queues create mantle-internal
77
- pnpm wrangler queues create mantle-internal-dlq
78
- ```
79
-
80
- Add both producer and consumer bindings. These are adapter settings, not Mantle
81
- manifest grammar:
82
-
83
- ```toml
84
- [[queues.producers]]
85
- binding = "MANTLE_INTERNAL_QUEUE"
86
- queue = "mantle-internal"
87
-
88
- [[queues.consumers]]
89
- queue = "mantle-internal"
90
- max_batch_size = 10
91
- max_batch_timeout = 5
92
- max_retries = 5
93
- retry_delay = 60
94
- dead_letter_queue = "mantle-internal-dlq"
95
- ```
96
-
97
- Wire the producer into `MantleCloudflareConfig.bindings` and export the consumer alongside
98
- the existing HTTP/OAuth handler. The same Worker may be both producer and
99
- consumer:
100
-
101
- ```ts
102
- import type { DeferredHookEnvelope } from "@aotter/mantle/runtime";
103
- import {
104
- WorkersQueueHookDispatcher,
105
- createMantleWorker,
106
- createQueueHandler,
107
- type MantleCloudflareEnv,
108
- } from "@aotter/mantle/cloudflare";
109
-
110
- interface Env extends MantleCloudflareEnv {
111
- DB: D1Database;
112
- MANTLE_INTERNAL_QUEUE: Queue<DeferredHookEnvelope>;
113
- }
114
-
115
- const worker = createMantleWorker<Env>({
116
- plan,
117
- handlers,
118
- bindings: (env, conventional) => ({
119
- ...conventional,
120
- deferredHookDispatcher: new WorkersQueueHookDispatcher(
121
- env.MANTLE_INTERNAL_QUEUE,
122
- ),
123
- }),
124
- });
125
-
126
- export default {
127
- fetch: worker.fetch,
128
- queue(batch, env) {
129
- return createQueueHandler<Env>({
130
- get: () => worker.getRuntime(env),
131
- })(batch, env);
132
- },
133
- } satisfies ExportedHandler<Env>;
134
- ```
135
-
136
- Queue opt-in adds only the dispatcher binding and `queue` export; the facade
137
- keeps Auth, MCP, cache, and runtime assembly on the standard path.
138
-
139
- ## Idempotent handlers
140
-
141
- For a D1-owned effect, make the event/Trigger key a unique database key. This
142
- example records a site-owned notification job exactly once even when the
143
- lifecycle handler is replayed:
144
-
145
- ```sql
146
- CREATE TABLE notification_jobs (
147
- idempotency_key TEXT PRIMARY KEY,
148
- entry_id TEXT NOT NULL,
149
- payload TEXT NOT NULL,
150
- created_at INTEGER NOT NULL
151
- );
152
- ```
153
-
154
- ```ts
155
- import type { HandlerContext } from "@aotter/mantle/runtime";
156
-
157
- async function enqueueNotification(
158
- input: Record<string, unknown>,
159
- ctx: HandlerContext,
160
- ) {
161
- if (!ctx.event?.entry) throw new Error("lifecycle entry event required");
162
- const key = `${ctx.event.id}:${ctx.event.trigger}`;
163
- await (ctx.env as Env).DB.prepare(
164
- `INSERT OR IGNORE INTO notification_jobs
165
- (idempotency_key, entry_id, payload, created_at)
166
- VALUES (?, ?, ?, ?)`,
167
- ).bind(key, ctx.event.entry.id, JSON.stringify(input), Date.now()).run();
168
- return { ok: true };
169
- }
170
- ```
171
-
172
- For an upstream API that supports idempotency, send the same stable key and
173
- throw on failure so Queue retries it:
174
-
175
- ```ts
176
- const key = `${ctx.event!.id}:${ctx.event!.trigger}`;
177
- const response = await fetch("https://api.example.com/messages", {
178
- method: "POST",
179
- headers: {
180
- "Content-Type": "application/json",
181
- "Idempotency-Key": key,
182
- },
183
- body: JSON.stringify(input),
184
- });
185
- if (!response.ok) throw new Error(`message provider returned ${response.status}`);
186
- ```
187
-
188
- Core intentionally does not add a business outbox, payment state machine,
189
- provider retry wrapper, or generic job registry. Those remain site-owned.
190
-
191
- ## Multiplexing site queues
192
-
193
- One Worker can consume the Mantle queue and site-owned queues without a Core
194
- registry. Route on `batch.queue` in the Worker entry:
195
-
196
- ```ts
197
- const mantleQueue = createQueueHandler<Env>(cms);
198
-
199
- export default {
200
- fetch: http.fetch.bind(http),
201
- queue(batch, env, ctx) {
202
- switch (batch.queue) {
203
- case "mantle-internal":
204
- return mantleQueue(batch, env);
205
- case "billing-jobs":
206
- return consumeBilling(batch, env, ctx);
207
- default:
208
- batch.retryAll();
209
- console.error(`No consumer for queue '${batch.queue}'`);
210
- return;
211
- }
212
- },
213
- } satisfies ExportedHandler<Env>;
214
- ```
215
-
216
- The Mantle consumer processes at most five messages concurrently within each
217
- delivered batch even if `max_batch_size` is raised. It calls `retryAll()` when
218
- runtime boot fails, `retry()` for one failed event, and `ack()` exactly once
219
- after success.
220
-
221
- ## Verification and operations
222
-
223
- Run the integrated contract test:
224
-
225
- ```sh
226
- pnpm --filter @aotter/mantle-cloudflare test -- mantle-internal-queue.test.ts
227
- ```
228
-
229
- It covers real runtime failure propagation, all-Trigger execution, stable
230
- replay identity, per-message ack/retry, malformed bodies, size/JSON rejection,
231
- runtime boot failure, and the five-message concurrency bound.
232
-
233
- For a site integration, run `wrangler dev`, submit a mutation with an
234
- `after_*` Trigger, and verify the consumer log. Make one handler fail once and
235
- confirm its first delivery is retried and the replay sees the same
236
- `ctx.event.id` and `ctx.event.trigger`. Inspect the DLQ before increasing
237
- `max_retries`; poison messages otherwise hide as repeated retries.
238
-
239
- ### Upgrading from the unversioned envelope
240
-
241
- Version 1 intentionally rejects the old unversioned envelope and the old
242
- `originalInput` field. Before deploying this release to a Worker that already
243
- uses `mantle-internal`, stop old producers and drain the queue (including any
244
- in-flight retries), or explicitly move the remaining messages to an operator
245
- workflow. Deploying v1 with legacy messages still queued will retry those
246
- messages and eventually place them in the DLQ.
5
+ The handbook ships inside the `@aotter/mantle` package under `node_modules/@aotter/mantle/docs/handbook/` and describes the installed release.