@aotter/mantle 0.1.0-alpha.9 → 0.1.2-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -38
- package/dist/cli/generate.d.ts +10 -1
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +49 -4
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/main.js +0 -8
- package/dist/cli/main.js.map +1 -1
- package/docs/adapter-guide.md +20 -24
- package/docs/adr/0008-structured-diagnostic-shape.md +7 -2
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +100 -0
- package/docs/adr/0018-core-starters-repository-boundary.md +3 -0
- package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +37 -1
- package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +199 -0
- package/docs/adr/0021-retire-starter-scaffolding.md +30 -0
- package/docs/adr/0022-caller-observed-version-occ.md +212 -0
- package/docs/adr/0023-port-failure-contract.md +69 -0
- package/docs/adr/README.md +18 -3
- package/docs/adr/adr-lite-803-request-diagnostics.md +38 -0
- package/docs/adr/adr-lite-808-route-readiness.md +47 -0
- package/docs/adr/adr-lite-809-bounded-public-content.md +71 -0
- package/docs/adr/adr-lite-812-native-parity.md +149 -0
- package/docs/adr/adr-lite-823-home-statistics.md +63 -0
- package/docs/adr/adr-lite-842-mcp-authoring.md +30 -0
- package/docs/adr/adr-lite-861-admin-webmcp.md +42 -0
- package/docs/adr/adr-lite-909-admin-ui-kit.md +37 -0
- package/docs/api-mcp-authorization.md +3 -664
- package/docs/auth-hosting-model.md +4 -4
- package/docs/cloudflare-low-level-composition.md +3 -104
- package/docs/deferred-lifecycle-queues.md +2 -243
- package/docs/design-atoms.md +16 -796
- package/docs/direct-authoring.md +5 -0
- package/docs/examples/minimal-worker/README.md +37 -0
- package/docs/examples/minimal-worker/manifests/site.yaml +25 -0
- package/docs/examples/minimal-worker/package.json +26 -0
- package/docs/examples/minimal-worker/smoke.mjs +23 -0
- package/docs/examples/minimal-worker/src/index.ts +4 -0
- package/docs/examples/minimal-worker/tsconfig.json +17 -0
- package/docs/examples/minimal-worker/wrangler.jsonc +22 -0
- package/docs/handbook/cloudflare/authentication.md +167 -0
- package/docs/handbook/cloudflare/bindings.md +233 -0
- package/docs/handbook/cloudflare/conventional-worker.md +162 -0
- package/docs/handbook/cloudflare/deferred-hooks-queues.md +171 -0
- package/docs/handbook/cloudflare/deploy-and-operate.md +98 -0
- package/docs/handbook/cloudflare/low-level-composition.md +169 -0
- package/docs/handbook/cloudflare/media-r2.md +216 -0
- package/docs/handbook/cloudflare/public-web.md +144 -0
- package/docs/handbook/concepts/authorization.md +116 -0
- package/docs/handbook/concepts/four-atoms.md +173 -0
- package/docs/handbook/concepts/lifecycle-and-locales.md +142 -0
- package/docs/handbook/concepts/mcp-and-agents.md +109 -0
- package/docs/handbook/concepts/procedures-and-triggers.md +221 -0
- package/docs/handbook/concepts/runtime-and-adapters.md +177 -0
- package/docs/handbook/concepts/views.md +183 -0
- package/docs/handbook/examples/commerce-transaction.md +813 -0
- package/docs/handbook/examples/guarded-api.md +427 -0
- package/docs/handbook/examples/intake-form.md +319 -0
- package/docs/handbook/examples/legal-documents.md +218 -0
- package/docs/handbook/examples/procurement-approvals.md +240 -0
- package/docs/handbook/examples/publication.md +240 -0
- package/docs/handbook/examples/reservation.md +220 -0
- package/docs/handbook/navigation.json +63 -0
- package/docs/handbook/reference/authorization.md +295 -0
- package/docs/handbook/reference/diagnostics.md +183 -0
- package/docs/handbook/reference/manifest.md +132 -0
- package/docs/handbook/reference/procedure.md +263 -0
- package/docs/handbook/reference/schema.md +247 -0
- package/docs/handbook/reference/site-config.md +233 -0
- package/docs/handbook/reference/surface.md +207 -0
- package/docs/handbook/reference/trigger.md +193 -0
- package/docs/handbook/reference/view.md +256 -0
- package/docs/handbook/start/project-and-cli.md +122 -0
- package/docs/handbook/start/quickstart-worker.md +193 -0
- package/docs/labels.md +3 -1
- package/docs/media-uploads.md +3 -184
- package/docs/migration-0.1.2.md +34 -1
- package/docs/performance-harness.md +125 -8
- package/docs/release-process.md +85 -231
- package/docs/schema-indexes.md +3 -180
- package/docs/sealed-pipeline-ownership.md +5 -4
- package/docs/spec-only-host-adoption.md +158 -0
- package/docs/transaction-patterns.md +5 -0
- package/package.json +21 -25
- package/skills/README.md +4 -4
- package/skills/develop/SKILL.md +23 -28
- package/skills/install/SKILL.md +55 -147
- package/skills/provision/SKILL.md +4 -3
- package/skills/theme/SKILL.md +6 -6
- package/skills/update/SKILL.md +32 -68
- package/dist/cli/create.d.ts +0 -2
- package/dist/cli/create.d.ts.map +0 -1
- package/dist/cli/create.js +0 -243
- package/dist/cli/create.js.map +0 -1
- package/dist/cli/update.d.ts +0 -2
- package/dist/cli/update.d.ts.map +0 -1
- package/dist/cli/update.js +0 -413
- package/dist/cli/update.js.map +0 -1
- package/dist/provision/renderProvisionBundle.d.ts +0 -70
- package/dist/provision/renderProvisionBundle.d.ts.map +0 -1
- package/dist/provision/renderProvisionBundle.js +0 -367
- package/dist/provision/renderProvisionBundle.js.map +0 -1
- package/dist/provision.d.ts +0 -2
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -2
- package/dist/provision.js.map +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Assemble a Mantle Worker with createMantleWorker, add handlers and routes through extend, and respect Core-owned paths.
|
|
3
|
+
---
|
|
4
|
+
# The conventional Worker
|
|
5
|
+
|
|
6
|
+
`createMantleWorker` is the normal Cloudflare entry for a Mantle application. This page covers its options, what the facade owns, how to add application handlers and routes, and the readiness and reserved-path rules that keep those extensions safe.
|
|
7
|
+
|
|
8
|
+
## Minimal entry
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createMantleWorker } from "@aotter/mantle/cloudflare";
|
|
12
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
13
|
+
|
|
14
|
+
export default createMantleWorker({ plan, cacheScope: "my-site-production" });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`plan` is the sealed plan that `mantle generate` writes to `.mantle/generated/mantle.ts`. With only `plan`, the Worker serves public View REST, HTTP Triggers, Admin, Auth, OAuth and MCP. It renders no public pages and `/` is a 404; see [Public web](./public-web.md).
|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
| Option | Type | Purpose |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `plan` | `RuntimePlan` | Required. Generated plan; a fingerprint or version mismatch fails immediately and asks you to regenerate. |
|
|
24
|
+
| `handlers` | `Record<string, AnyHandler>` | Implementations for `handler.kind: ref` Procedures. Merged with `extend().handlers`; a name registered twice throws. |
|
|
25
|
+
| `siteDefaults` | `SiteDefaults \| (env) => SiteDefaults` | Brand, title, description, origin, locales, icons, media purposes. Use the function form to read `env.PUBLIC_ORIGIN`. See [Site config](../reference/site-config.md). |
|
|
26
|
+
| `cacheScope` | `string \| (env) => string` | Stable deployment/site identifier for public cache tags and optional `MANTLE_KV` keys. Lowercase letters, digits, `_` and `-`, up to 64 characters. Shared caching is disabled when absent or invalid. |
|
|
27
|
+
| `templates` | `TemplateRegistry` | Entry and list templates for public HTML. |
|
|
28
|
+
| `publicPathResolver` | `PublicPathResolver` | Collection-to-URL mapping used for canonical URLs, sitemap and hreflang. |
|
|
29
|
+
| `mediaAllowSvg` | `boolean \| (env) => boolean` | Accept SVG uploads. Default `false`. |
|
|
30
|
+
| `auth` | `(env) => Auth` | Replace Auth construction only. Core still owns the Auth routes. |
|
|
31
|
+
| `bindings` | `(env, conventional) => MantleWorkerBindings` | Augment the conventional adapters, for example `mediaStorage` or `deferredHookDispatcher`. |
|
|
32
|
+
| `extend` | `(ctx) => MantleWorkerExtension \| void` | The one seam for handlers, credential resolution, JWT bearer verification and new Hono routes. |
|
|
33
|
+
|
|
34
|
+
`extend` receives `{ env, auth, bindings, getRuntime }` and may return:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
{
|
|
38
|
+
handlers?: Record<string, AnyHandler>;
|
|
39
|
+
credentialResolver?: ConsumerCredentialResolver; // site-owned API keys and personal tokens
|
|
40
|
+
jwtBearer?: { audience: string; scopes?: readonly string[] };
|
|
41
|
+
mount?: ({ app, ref, env, auth, bindings, getRuntime }) => void;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`extend` may run again after a failed initialization. Keep external side effects out of it.
|
|
46
|
+
|
|
47
|
+
## What the facade owns
|
|
48
|
+
|
|
49
|
+
Once per isolate, `createMantleWorker` assembles and memoizes:
|
|
50
|
+
|
|
51
|
+
- Conventional bindings: `DB` becomes the D1 driver, `ASSETS` serves the Admin bundle, `MANTLE_KV` (when bound) becomes the MCP catalog projection. See [Bindings](./bindings.md).
|
|
52
|
+
- Conventional Auth chosen by `MANTLE_AUTH_MODE`, or your `auth` factory. See [Authentication](./authentication.md).
|
|
53
|
+
- Runtime endpoints: manifest HTTP Triggers, `GET /api/views` and `GET /api/views/<name>` for public Views.
|
|
54
|
+
- Admin at `/admin` when Admin assets are present, OAuth consent and discovery, and MCP at `/mcp` and `/mcp/staff`.
|
|
55
|
+
- A `/favicon.ico` route derived from `siteDefaults.icons`. This is a convention, not a reserved path; an existing host route wins.
|
|
56
|
+
- The final cache policy on every response, and best-effort purge of the deployment-scoped public tag after publishing-content and site-setting writes.
|
|
57
|
+
- A redacted error boundary: an unexpected failure returns `500` with `{ "ok": false, "error": "internal_error" }` and `private, no-store`.
|
|
58
|
+
|
|
59
|
+
If `auth.ready` rejects, the memoized assembly is evicted so the next request rebuilds instead of reusing a poisoned isolate.
|
|
60
|
+
|
|
61
|
+
## The returned handler
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const worker = createMantleWorker<Env>({ plan });
|
|
65
|
+
worker.fetch(request, env, ctx); // HTTP entry
|
|
66
|
+
worker.getRuntime(env); // the runtime fetch uses, resolved after auth.ready
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Queue and scheduled handlers call `worker.getRuntime(env)` and then generated `bindMantle(runtime)` so they reuse the assembled runtime and never write Mantle tables directly. See [Bindings](./bindings.md#cron-triggers) and [Deferred hooks](./deferred-hooks-queues.md).
|
|
70
|
+
|
|
71
|
+
## Readiness rule
|
|
72
|
+
|
|
73
|
+
Standard protected routes establish readiness themselves: the facade awaits `getRuntime()` before Auth, `/oauth`, `/mcp`, `/admin/api` and `/.well-known/oauth*` requests. Extension routes do not. Before an extension route reads or writes Mantle data, or relies on database-backed Auth, it must `await ref.get()` (inside `mount`) or `await getRuntime()`.
|
|
74
|
+
|
|
75
|
+
`getRuntime` rejects when called synchronously inside `extend` before it returns. Retain the function and call it later, for example inside a request handler.
|
|
76
|
+
|
|
77
|
+
## Reserved paths
|
|
78
|
+
|
|
79
|
+
Extensions add routes; they never replace Core surfaces. These registrations are rejected:
|
|
80
|
+
|
|
81
|
+
- `/admin`, `/_mantle`, `/api/auth`, `/api/views`, `/oauth`, `/mcp`, and anything beneath them
|
|
82
|
+
- `/.well-known/oauth*`
|
|
83
|
+
- the custom Auth factory's `basePath`
|
|
84
|
+
- global `*` and `/*` handlers
|
|
85
|
+
- any exact method and path pair that a manifest HTTP Trigger already owns
|
|
86
|
+
|
|
87
|
+
Static literals fail TypeScript during your build (`MantleExtensionPath`). Computed paths cannot be proven statically, so after `mount` returns the facade inspects Hono's assembled route table and throws before serving any request. There is no override option. The `app` passed to `mount` exposes `get`, `post`, `put`, `patch`, `delete`, `options`, `all`, `on`, `use` and `route`; it omits global error and not-found hooks.
|
|
88
|
+
|
|
89
|
+
## Handlers and routes
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createMantleWorker, type MantleCloudflareEnv } from "@aotter/mantle/cloudflare";
|
|
93
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
94
|
+
import { notifyIntake } from "./handlers/notifyIntake.js";
|
|
95
|
+
|
|
96
|
+
interface Env extends MantleCloudflareEnv {
|
|
97
|
+
readonly PUBLIC_ORIGIN: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default createMantleWorker<Env>({
|
|
101
|
+
plan,
|
|
102
|
+
siteDefaults: (env) => ({
|
|
103
|
+
brand: "Example",
|
|
104
|
+
title: "Example",
|
|
105
|
+
description: "Example site.",
|
|
106
|
+
origin: env.PUBLIC_ORIGIN,
|
|
107
|
+
locales: ["en"],
|
|
108
|
+
icons: [
|
|
109
|
+
{ src: "/site-icon.png", mimeType: "image/png", sizes: ["64x64"] },
|
|
110
|
+
{ src: "/site-icon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
|
|
111
|
+
],
|
|
112
|
+
}),
|
|
113
|
+
extend: () => ({
|
|
114
|
+
handlers: { "notify-intake": notifyIntake },
|
|
115
|
+
mount({ app, ref }) {
|
|
116
|
+
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
117
|
+
app.get("/api/locales", async (c) => {
|
|
118
|
+
const runtime = await ref.get(); // readiness before data
|
|
119
|
+
const site = await runtime.siteConfig.load();
|
|
120
|
+
return c.json({ locales: site.locales });
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Handler keys match `spec.handler.ref` in Procedure manifests; see [Procedures and Triggers](../concepts/procedures-and-triggers.md).
|
|
128
|
+
|
|
129
|
+
## A `bindings` hook
|
|
130
|
+
|
|
131
|
+
`bindings` receives the conventional set and returns the set the runtime uses. Spread the conventional bindings, then add capability adapters:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
createMantleWorker,
|
|
136
|
+
WorkersQueueHookDispatcher,
|
|
137
|
+
} from "@aotter/mantle/cloudflare";
|
|
138
|
+
|
|
139
|
+
export default createMantleWorker<Env>({
|
|
140
|
+
plan,
|
|
141
|
+
bindings: (env, conventional) => ({
|
|
142
|
+
...conventional,
|
|
143
|
+
mediaStorage: buildMediaStorage(env), // see media-r2.md
|
|
144
|
+
deferredHookDispatcher: new WorkersQueueHookDispatcher(env.MANTLE_INTERNAL_QUEUE),
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The conventional set is `{ db, adminAssets, mcpCatalogKv? }`. Never drop `db`.
|
|
150
|
+
|
|
151
|
+
## Site identity
|
|
152
|
+
|
|
153
|
+
`siteDefaults.icons` is one identity reused by browser favicons, Admin chrome and MCP `serverInfo.icons`. Keep the SVG as the editable source and add a PNG rendition for MCP clients that need raster formats. Both files live in `public/`.
|
|
154
|
+
|
|
155
|
+
## Source
|
|
156
|
+
- [`packages/mantle/README.md`](../../../packages/mantle/README.md)
|
|
157
|
+
- [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
|
|
158
|
+
- [`packages/adapters/cloudflare/src/worker/createMantleWorker.ts`](../../../packages/adapters/cloudflare/src/worker/createMantleWorker.ts)
|
|
159
|
+
- [`packages/adapters/cloudflare/src/bindings/conventionalBindings.ts`](../../../packages/adapters/cloudflare/src/bindings/conventionalBindings.ts)
|
|
160
|
+
- [`packages/adapters/cloudflare/src/mount/cmsConfig.ts`](../../../packages/adapters/cloudflare/src/mount/cmsConfig.ts)
|
|
161
|
+
- [`docs/examples/minimal-worker/src/index.ts`](../../../docs/examples/minimal-worker/src/index.ts)
|
|
162
|
+
- [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Move after_* lifecycle Triggers onto Cloudflare Queues with at-least-once delivery, idempotent handlers and a DLQ.
|
|
3
|
+
---
|
|
4
|
+
# Deferred hooks with Queues
|
|
5
|
+
|
|
6
|
+
`after_create`, `after_update`, `after_delete` and `after_publish` lifecycle Triggers run inline by default. Binding a Cloudflare Queue moves them off the request path. This page covers the guarantees, the setup, the Worker wiring and how to write handlers that survive replay.
|
|
7
|
+
|
|
8
|
+
## When to defer
|
|
9
|
+
|
|
10
|
+
Defer hooks that must not add request latency and that tolerate a short delay: notifications, projections, upstream API calls. Keep synchronous `before_*` hooks for validation and abuse prevention; they may read request-only input such as a CAPTCHA token, which deferred envelopes never retain.
|
|
11
|
+
|
|
12
|
+
## Guarantees
|
|
13
|
+
|
|
14
|
+
- The entry write commits first. D1 and the Queue do not share a transaction, so a Worker failure between them can lose the enqueue.
|
|
15
|
+
- Delivery is at-least-once. A message can run more than once.
|
|
16
|
+
- A rejected or ambiguous `Queue.send()` falls back to `ctx.waitUntil` (or inline when unavailable) with the same event identity. That fallback is best-effort.
|
|
17
|
+
- A consumer runs every captured Trigger before acknowledging; a later failure can replay an earlier success.
|
|
18
|
+
- Malformed, oversized, removed-Trigger and persistently failing messages are retried and then land in the dead-letter queue. Without a DLQ, Cloudflare discards them after `max_retries`.
|
|
19
|
+
|
|
20
|
+
Handlers must therefore be idempotent. Mantle does not promise exactly-once execution.
|
|
21
|
+
|
|
22
|
+
## The v1 envelope
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
interface DeferredHookEnvelope {
|
|
26
|
+
version: 1;
|
|
27
|
+
eventId: string;
|
|
28
|
+
triggerNames: readonly string[];
|
|
29
|
+
hook: "after_create" | "after_update" | "after_delete" | "after_publish";
|
|
30
|
+
schema: string;
|
|
31
|
+
entry: EntryRow;
|
|
32
|
+
ctxSnapshot: CtxSnapshot | null;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Each handler receives the persisted `entry.data` as input and `ctx.event = { id, trigger, hook, schema, entry }`. Use `${ctx.event.id}:${ctx.event.trigger}` as the idempotency key; it is stable across fallback, retries and replay. `ctxSnapshot` holds normalized actor and credential metadata, never cookies, tokens or API keys.
|
|
37
|
+
|
|
38
|
+
Cloudflare's 128 KB message limit is decimal and includes platform metadata. The dispatcher rejects non-JSON-safe envelopes and any encoded envelope of 127,000 bytes or more before calling `Queue.send`. Keep entry payloads well below that.
|
|
39
|
+
|
|
40
|
+
## Setup
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
pnpm wrangler queues create mantle-internal
|
|
44
|
+
pnpm wrangler queues create mantle-internal-dlq
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
"queues": {
|
|
49
|
+
"producers": [{ "binding": "MANTLE_INTERNAL_QUEUE", "queue": "mantle-internal" }],
|
|
50
|
+
"consumers": [{
|
|
51
|
+
"queue": "mantle-internal",
|
|
52
|
+
"max_batch_size": 10,
|
|
53
|
+
"max_batch_timeout": 5,
|
|
54
|
+
"max_retries": 5,
|
|
55
|
+
"retry_delay": 60,
|
|
56
|
+
"dead_letter_queue": "mantle-internal-dlq"
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
These are adapter settings, not manifest grammar. See [Trigger](../reference/trigger.md) for the `lifecycle` source shape.
|
|
62
|
+
|
|
63
|
+
## Worker wiring
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import type { DeferredHookEnvelope } from "@aotter/mantle/runtime";
|
|
67
|
+
import {
|
|
68
|
+
WorkersQueueHookDispatcher,
|
|
69
|
+
createMantleWorker,
|
|
70
|
+
createQueueHandler,
|
|
71
|
+
type MantleCloudflareEnv,
|
|
72
|
+
} from "@aotter/mantle/cloudflare";
|
|
73
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
74
|
+
|
|
75
|
+
interface Env extends MantleCloudflareEnv {
|
|
76
|
+
readonly MANTLE_INTERNAL_QUEUE: Queue<DeferredHookEnvelope>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const worker = createMantleWorker<Env>({
|
|
80
|
+
plan,
|
|
81
|
+
handlers,
|
|
82
|
+
bindings: (env, conventional) => ({
|
|
83
|
+
...conventional,
|
|
84
|
+
deferredHookDispatcher: new WorkersQueueHookDispatcher(env.MANTLE_INTERNAL_QUEUE),
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export default {
|
|
89
|
+
fetch: worker.fetch,
|
|
90
|
+
queue(batch, env) {
|
|
91
|
+
return createQueueHandler<Env>({ get: () => worker.getRuntime(env) })(batch, env);
|
|
92
|
+
},
|
|
93
|
+
} satisfies ExportedHandler<Env>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The same Worker is producer and consumer. Opt-in adds only the dispatcher binding and the `queue` export; Auth, MCP, cache and runtime assembly stay on the standard path.
|
|
97
|
+
|
|
98
|
+
## Multiplexing application queues
|
|
99
|
+
|
|
100
|
+
Route on `batch.queue` when the Worker also consumes its own queues:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
queue(batch, env, ctx) {
|
|
104
|
+
const mantleQueue = createQueueHandler<Env>({ get: () => worker.getRuntime(env) });
|
|
105
|
+
switch (batch.queue) {
|
|
106
|
+
case "mantle-internal": return mantleQueue(batch, env);
|
|
107
|
+
case "billing-jobs": return consumeBilling(batch, env, ctx);
|
|
108
|
+
default:
|
|
109
|
+
batch.retryAll();
|
|
110
|
+
console.error(`No consumer for queue '${batch.queue}'`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Application consumers own their own acknowledgement. A common pattern is to `bindMantle(await worker.getRuntime(env))` and call a Procedure, then `ack()` on success, `retry()` on `INTERNAL_ERROR` or `CONFLICT`, and `ack()` on other diagnostics so a poison message does not loop. See [Bindings](./bindings.md#queues).
|
|
116
|
+
|
|
117
|
+
## Consumer semantics
|
|
118
|
+
|
|
119
|
+
- At most five messages run concurrently within a delivered batch, even if `max_batch_size` is higher.
|
|
120
|
+
- Runtime boot failure calls `batch.retryAll()` so per-message attempt counters advance and `max_retries` and the DLQ apply.
|
|
121
|
+
- Each message is acknowledged exactly once after every captured Trigger succeeds; any failure calls `retry()` on that message only.
|
|
122
|
+
|
|
123
|
+
## An idempotent D1 handler
|
|
124
|
+
|
|
125
|
+
Make the event key a unique database key:
|
|
126
|
+
|
|
127
|
+
```sql
|
|
128
|
+
CREATE TABLE notification_jobs (
|
|
129
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
130
|
+
entry_id TEXT NOT NULL,
|
|
131
|
+
payload TEXT NOT NULL,
|
|
132
|
+
created_at INTEGER NOT NULL
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import type { HandlerContext } from "@aotter/mantle/runtime";
|
|
138
|
+
|
|
139
|
+
export async function enqueueNotification(input: Record<string, unknown>, ctx: HandlerContext) {
|
|
140
|
+
if (!ctx.event?.entry) throw new Error("lifecycle entry event required");
|
|
141
|
+
const key = `${ctx.event.id}:${ctx.event.trigger}`;
|
|
142
|
+
await (ctx.env as Env).DB.prepare(
|
|
143
|
+
`INSERT OR IGNORE INTO notification_jobs (idempotency_key, entry_id, payload, created_at)
|
|
144
|
+
VALUES (?, ?, ?, ?)`,
|
|
145
|
+
).bind(key, ctx.event.entry.id, JSON.stringify(input), Date.now()).run();
|
|
146
|
+
return { ok: true };
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For an upstream API, send the same key as `Idempotency-Key` and throw on a non-OK response so the Queue retries. Core adds no outbox, job registry or provider retry wrapper; those remain application-owned.
|
|
151
|
+
|
|
152
|
+
## The removed-Trigger footgun
|
|
153
|
+
|
|
154
|
+
`triggerNames` is captured when the mutation runs. Renaming or removing a captured Trigger makes every older message fail validation at dispatch and eventually reach the DLQ; it never silently changes meaning. Drain the queue before removing a deferred Trigger, and inspect the DLQ before raising `max_retries`.
|
|
155
|
+
|
|
156
|
+
## Verify
|
|
157
|
+
|
|
158
|
+
In the SDK checkout the contract test is:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
pnpm --filter @aotter/mantle-cloudflare test -- mantle-internal-queue.test.ts
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For a site, run `wrangler dev`, submit a mutation that has an `after_*` Trigger, and read the consumer log. Make one handler fail once and confirm the replay carries the same `ctx.event.id` and `ctx.event.trigger`.
|
|
165
|
+
|
|
166
|
+
## Source
|
|
167
|
+
- [`docs/deferred-lifecycle-queues.md`](../../../docs/deferred-lifecycle-queues.md)
|
|
168
|
+
- [`packages/adapters/cloudflare/src/bindings/WorkersQueueHookDispatcher.ts`](../../../packages/adapters/cloudflare/src/bindings/WorkersQueueHookDispatcher.ts)
|
|
169
|
+
- [`packages/adapters/cloudflare/src/mount/cmsConfig.ts`](../../../packages/adapters/cloudflare/src/mount/cmsConfig.ts)
|
|
170
|
+
- [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
|
|
171
|
+
- Retired-starter pattern: [`overlays/transaction/src/index.ts`](https://github.com/aotter/mantle-starters/blob/a66ec0ea3aaefc09a0229b7d8ca35af630f2b55d/overlays/transaction/src/index.ts)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Production checklist, the check loop, deploy and post-deploy probes, day-to-day content operations, and upgrades.
|
|
3
|
+
---
|
|
4
|
+
# Deploy and operate
|
|
5
|
+
|
|
6
|
+
This page is the production checklist for a Mantle Worker on Cloudflare: what to pin and configure, which checks to run before `wrangler deploy`, how to verify a deployment, how content is operated afterwards, and how to upgrade.
|
|
7
|
+
|
|
8
|
+
## Before the first deploy
|
|
9
|
+
|
|
10
|
+
- Pin every `@aotter/mantle*` package to one exact version and commit the lockfile. Install with `pnpm install --frozen-lockfile` (or `npm ci`) from then on. See [Project and CLI](../start/project-and-cli.md).
|
|
11
|
+
- Set `PUBLIC_ORIGIN` to the real HTTPS origin, without a trailing slash. It drives canonical URLs, `.md` mirrors, `llms.txt`, the MCP resource and the OAuth callback. If a static documentation build also emits absolute URLs, give it the same value.
|
|
12
|
+
- Set the production D1 `database_id` (and `account_id` if your deployment needs it) in `wrangler.jsonc`. A local `database_name` is not a production identifier, and local data is not migrated.
|
|
13
|
+
- Choose `MANTLE_AUTH_MODE` and store the secrets with `wrangler secret put`. See [Authentication](./authentication.md).
|
|
14
|
+
- Keep `compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"]`.
|
|
15
|
+
- Enable observability:
|
|
16
|
+
|
|
17
|
+
```jsonc
|
|
18
|
+
"observability": { "enabled": true, "logs": { "head_sampling_rate": 1 } },
|
|
19
|
+
"upload_source_maps": true
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The check loop
|
|
23
|
+
|
|
24
|
+
Run the project's `check` script before every deploy. The minimal reference chains:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
mantle generate && mantle generate --check && mantle validate \
|
|
28
|
+
&& mantle skills && mantle skills --check && tsc --noEmit && node smoke.mjs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Add tests and the frontend build where the project has them, then confirm index coverage for every public View:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pnpm exec mantle-harness indexes --require-public --format text
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A required path fails on an `entries` table scan, a temporary sort or an unindexed data-field predicate. Then dry-run and deploy:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
wrangler deploy --dry-run
|
|
41
|
+
wrangler deploy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Post-deploy verification
|
|
45
|
+
|
|
46
|
+
Probe the deployed origin, not `wrangler dev`:
|
|
47
|
+
|
|
48
|
+
| Probe | Expect |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `GET /api/views/<public-view>` | `200`, `{ "ok": true, "data": { "rows": [...] } }` |
|
|
51
|
+
| `GET /<locale>/<segment>/<slug>` and `GET /<locale>/<segment>/<slug>.md` | `200` HTML and Markdown for a published entry |
|
|
52
|
+
| `GET /llms.txt`, `GET /sitemap.xml`, `GET /robots.txt` | `200` |
|
|
53
|
+
| `GET /<locale>/<segment>/does-not-exist` | `404` from your `notFoundRenderer` |
|
|
54
|
+
| `GET /mcp` without credentials | `401` with `WWW-Authenticate` |
|
|
55
|
+
| `GET /admin` | Sign-in page; sign in with the `ADMIN_GITHUB_LOGIN` account |
|
|
56
|
+
|
|
57
|
+
Then check the cache: a second anonymous `GET` of a public page should show `cf-cache-status: HIT`; publish a change in Admin and the next request should be a `MISS`. Sample latency with:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pnpm exec mantle-harness http --base-url https://example.com \
|
|
61
|
+
--route recent=/api/views/recent-posts --route page=/en/posts/hello --rounds 20 --warmup 2
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Operating content
|
|
65
|
+
|
|
66
|
+
Sign in to Admin with a staff account. Publishing collections (`lifecycle: publishing`) follow draft, publish, verify:
|
|
67
|
+
|
|
68
|
+
1. Create a draft with its title, slug, locale and body.
|
|
69
|
+
2. Publish (`editor` or above). The write purges the deployment-scoped public cache tag.
|
|
70
|
+
3. Open the public URL and its `.md` mirror. Drafts never appear on pages, mirrors, `llms.txt` or the sitemap; use `?preview=1` with a staff session to see one.
|
|
71
|
+
4. Unpublish removes the entry from every public surface; the Admin delete action and the Staff MCP `archive_entry` tool retire it.
|
|
72
|
+
|
|
73
|
+
Operational collections (`lifecycle: operational`) have no publish step; records are edited in place and do not purge the public cache. The same operations are available to agents through Staff MCP; see [MCP and agents](../concepts/mcp-and-agents.md).
|
|
74
|
+
|
|
75
|
+
Site settings split by owner. Brand, title and description seed once from `siteDefaults` and are then edited in Admin (`owner`); each edit purges the public cache. Origin, icons, locales and media purposes are code-owned and re-sync from `siteDefaults` on every boot, so change them in the Worker and redeploy. See [Site config](../reference/site-config.md).
|
|
76
|
+
|
|
77
|
+
## Upgrading
|
|
78
|
+
|
|
79
|
+
Read the migration notes shipped with the target release before changing versions; docs on the development branch do not describe your installed version. The 0.1.2 line removes `mantle create`, the bundle `mantle update` and `@aotter/mantle/provision`; `generate`, `validate`, `emit-openapi` and `skills` remain. To upgrade:
|
|
80
|
+
|
|
81
|
+
1. Pin the new exact release for every selected package and update the lockfile through the package manager; review peer upgrades.
|
|
82
|
+
2. Remove scripts that call retired commands. Keep application source, Worker/D1/KV identity, origins, auth mode and secrets.
|
|
83
|
+
3. Run `mantle generate`, `generate --check`, `skills`, `skills --check`, `validate`, typecheck and tests.
|
|
84
|
+
4. Test local routes and authorization, then deploy.
|
|
85
|
+
|
|
86
|
+
## Source
|
|
87
|
+
- [`packages/mantle/README.md`](../../../packages/mantle/README.md)
|
|
88
|
+
- [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
|
|
89
|
+
- [`docs/direct-authoring.md`](../../../docs/direct-authoring.md)
|
|
90
|
+
- [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
|
|
91
|
+
- [`docs/performance-harness.md`](../../../docs/performance-harness.md)
|
|
92
|
+
- [`docs/examples/minimal-worker/README.md`](../../../docs/examples/minimal-worker/README.md)
|
|
93
|
+
- [`docs/examples/minimal-worker/package.json`](../../../docs/examples/minimal-worker/package.json)
|
|
94
|
+
- [`docs/examples/minimal-worker/wrangler.jsonc`](../../../docs/examples/minimal-worker/wrangler.jsonc)
|
|
95
|
+
- [`docs/examples/minimal-worker/smoke.mjs`](../../../docs/examples/minimal-worker/smoke.mjs)
|
|
96
|
+
- [`packages/mantle-spec/src/domain/model/SiteConfig.ts`](../../../packages/mantle-spec/src/domain/model/SiteConfig.ts)
|
|
97
|
+
- [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
|
|
98
|
+
- [`packages/adapters/cloudflare/src/oauth/cachePolicy.ts`](../../../packages/adapters/cloudflare/src/oauth/cachePolicy.ts)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Own the top-level Hono assembly with the same public primitives createMantleWorker uses, and the invariants you must keep.
|
|
3
|
+
---
|
|
4
|
+
# Low-level composition
|
|
5
|
+
|
|
6
|
+
Use `createMantleWorker` unless the deployment must own its top-level Worker lifecycle. This page covers when that is true, what it costs, the exported building blocks, the assembly order, and the invariants a hand-assembled Worker must preserve.
|
|
7
|
+
|
|
8
|
+
## When you need it
|
|
9
|
+
|
|
10
|
+
You need low-level composition when the Worker entry must do something the facade's single `extend` seam cannot: run logic around every response (an audit queue, custom telemetry), select bindings or Auth per request, or interleave Mantle with another framework's router. Custom handlers, routes, credential resolvers and capability bindings do not require it; see [The conventional Worker](./conventional-worker.md).
|
|
11
|
+
|
|
12
|
+
The cost is ownership. The Worker entry, error boundary, readiness handling and cache boundary become application code, and there is no automatic merge path back to `createMantleWorker`. Mantle still owns the imported adapters and standard route behavior; update them through the package version and never copy their source.
|
|
13
|
+
|
|
14
|
+
## Building blocks
|
|
15
|
+
|
|
16
|
+
All of these are exported from `@aotter/mantle/cloudflare` unless noted:
|
|
17
|
+
|
|
18
|
+
| Export | Role |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `createConventionalBindings(env, cacheScope?)` | `DB` to D1 driver, `ASSETS` to Admin assets, optional deployment-scoped `MANTLE_KV` |
|
|
21
|
+
| `createConventionalAuth(env)`, `createAuth(config)` | Mode-driven Auth, or a curated custom factory |
|
|
22
|
+
| `conventionalMcpResource(env)`, `conventionalAuthBaseURL(env)` | `<PUBLIC_ORIGIN>/mcp` and the canonical base URL |
|
|
23
|
+
| `setupIncompleteAuthResponse(request, auth)` | The `503 setup_incomplete` guard for Auth-owned paths |
|
|
24
|
+
| `createMantleRuntimeRef(config)` | Per-isolate runtime singleton with `get()`, `web()`, `auth`, `plan` |
|
|
25
|
+
| `mountRuntimeEndpoints(app, ref)` | HTTP Triggers, `GET /api/views`, `GET /api/views/<name>` |
|
|
26
|
+
| `mountAdmin(app, ref, assets)` | Admin routes and SPA |
|
|
27
|
+
| `mountMantleOAuth(app, { auth, assets })` | OAuth consent and discovery, from `@aotter/mantle/admin` |
|
|
28
|
+
| `createMcpApiHandler({ ref, surface, resource })` | `/mcp` (`"public"`) and `/mcp/staff` (`"staff"`) handlers |
|
|
29
|
+
| `mountPublicRoutes(app, ref, options)` | Public HTML, `.md`, `llms.txt`, sitemap |
|
|
30
|
+
| `applyCachePolicy(request, response, cacheTag?)` | The final cache decision |
|
|
31
|
+
| `runMantleWorkerRequest(fn)` | Redacted `500 internal_error` boundary |
|
|
32
|
+
| `D1DatabaseDriver`, `AssetsAssetServer`, `R2MediaStorage`, `WorkersQueueHookDispatcher`, `createQueueHandler`, `KvSiteConfigRepository`, `cloudflareTurnstileCheck`, `resolveCaller` | Individual adapters and helpers |
|
|
33
|
+
| `MANTLE_RESERVED_PATH_PREFIXES`, `MANTLE_RESERVED_WELL_KNOWN_PREFIX`, `MANTLE_RESERVED_EXACT_PATHS` | Core-owned path constants |
|
|
34
|
+
|
|
35
|
+
## Assembly
|
|
36
|
+
|
|
37
|
+
The order is bindings, Auth, runtime ref, Hono app, runtime endpoints, Admin, OAuth, MCP handlers, then the cache policy on every response. This example adds one application-owned audit message after each request:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Hono } from "hono";
|
|
41
|
+
import {
|
|
42
|
+
applyCachePolicy,
|
|
43
|
+
conventionalMcpResource,
|
|
44
|
+
createMantleRuntimeRef,
|
|
45
|
+
createConventionalAuth,
|
|
46
|
+
createConventionalBindings,
|
|
47
|
+
createMcpApiHandler,
|
|
48
|
+
mountAdmin,
|
|
49
|
+
mountRuntimeEndpoints,
|
|
50
|
+
runMantleWorkerRequest,
|
|
51
|
+
scopedPublicCacheTag,
|
|
52
|
+
setupIncompleteAuthResponse,
|
|
53
|
+
type MantleCloudflareEnv,
|
|
54
|
+
} from "@aotter/mantle/cloudflare";
|
|
55
|
+
import { mountMantleOAuth } from "@aotter/mantle/admin";
|
|
56
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
57
|
+
|
|
58
|
+
interface Env extends MantleCloudflareEnv {
|
|
59
|
+
readonly AUDIT_QUEUE: Queue<{ kind: "request-complete"; path: string; status: number }>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let assembled: ReturnType<typeof assemble> | undefined;
|
|
63
|
+
const CACHE_SCOPE = "my-site-production";
|
|
64
|
+
const CACHE_TAG = scopedPublicCacheTag(CACHE_SCOPE)!;
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
68
|
+
return runMantleWorkerRequest(async () => {
|
|
69
|
+
const worker = assembled ??= assemble(env);
|
|
70
|
+
if (worker.auth.ready) ctx.waitUntil(worker.auth.ready.catch((error) => {
|
|
71
|
+
if (assembled === worker) assembled = undefined; // evict a failed assembly
|
|
72
|
+
throw error;
|
|
73
|
+
}));
|
|
74
|
+
const incomplete = await setupIncompleteAuthResponse(request, worker.auth);
|
|
75
|
+
const response = incomplete ?? await worker.fetch(request, env, ctx);
|
|
76
|
+
ctx.waitUntil(env.AUDIT_QUEUE.send({
|
|
77
|
+
kind: "request-complete",
|
|
78
|
+
path: new URL(request.url).pathname,
|
|
79
|
+
status: response.status,
|
|
80
|
+
}));
|
|
81
|
+
return response;
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
} satisfies ExportedHandler<Env>;
|
|
85
|
+
|
|
86
|
+
function assemble(env: Env) {
|
|
87
|
+
const bindings = createConventionalBindings(env, CACHE_SCOPE);
|
|
88
|
+
const auth = createConventionalAuth(env);
|
|
89
|
+
const ref = createMantleRuntimeRef({
|
|
90
|
+
plan,
|
|
91
|
+
bindings,
|
|
92
|
+
auth,
|
|
93
|
+
cacheScope: CACHE_SCOPE,
|
|
94
|
+
onPublicChange: purgePublicCache,
|
|
95
|
+
});
|
|
96
|
+
const app = new Hono<{ Bindings: Env }>();
|
|
97
|
+
|
|
98
|
+
mountRuntimeEndpoints(app, ref);
|
|
99
|
+
if (bindings.adminAssets) mountAdmin(app, ref, bindings.adminAssets);
|
|
100
|
+
mountMantleOAuth(app, { auth, assets: bindings.adminAssets });
|
|
101
|
+
app.get("/cache-probe", () => new Response("public", {
|
|
102
|
+
headers: { "cache-control": "public, s-maxage=60" },
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
const resource = conventionalMcpResource(env);
|
|
106
|
+
const mcp = new Map([
|
|
107
|
+
["/mcp/staff", createMcpApiHandler<Env>({ ref, surface: "staff", resource })],
|
|
108
|
+
["/mcp", createMcpApiHandler<Env>({ ref, surface: "public", resource })],
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
auth,
|
|
113
|
+
async fetch(request: Request, workerEnv: Env, ctx: ExecutionContext) {
|
|
114
|
+
// Prepare the canonical D1 schema before Better Auth handles a
|
|
115
|
+
// token, client, consent or CIMD request.
|
|
116
|
+
await ref.get();
|
|
117
|
+
const handler = mcp.get(new URL(request.url).pathname);
|
|
118
|
+
const response = handler?.fetch
|
|
119
|
+
? await handler.fetch(request, workerEnv, ctx)
|
|
120
|
+
: await app.fetch(request, workerEnv, ctx);
|
|
121
|
+
return applyCachePolicy(request, response, CACHE_TAG);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function purgePublicCache() {
|
|
127
|
+
try {
|
|
128
|
+
const { cache } = await import("cloudflare:workers");
|
|
129
|
+
const result = await cache.purge({ tags: [CACHE_TAG] });
|
|
130
|
+
if (!result.success) console.error("public cache purge failed", result.errors);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error("public cache purge failed", error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```jsonc
|
|
138
|
+
{
|
|
139
|
+
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
|
|
140
|
+
"queues": { "producers": [{ "binding": "AUDIT_QUEUE", "queue": "my-site-audit" }] }
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Invariants to keep
|
|
145
|
+
|
|
146
|
+
- Await `ref.get()` before Better Auth handles a token, client, consent or CIMD request. The facade does this for Auth, `/oauth`, `/mcp`, `/admin/api` and `/.well-known/oauth*`; the example awaits before every request.
|
|
147
|
+
- Evict the memoized assembly when `auth.ready` rejects, so a transient D1 failure during boot does not poison the isolate.
|
|
148
|
+
- Pass every response through `applyCachePolicy`, after Admin, Auth, API, OAuth, MCP, application routes, redirects and errors. It marks private surfaces `private, no-store`, strips CDN override headers, and keeps only anonymous `200` `GET`/`HEAD` responses with explicit `public` freshness.
|
|
149
|
+
- Return `setupIncompleteAuthResponse` before dispatch so misconfigured Auth fails closed on Auth-owned paths only.
|
|
150
|
+
- Keep the conventional `DB` binding, both compatibility flags, and the optional `MANTLE_KV` binding. Low-level bindings may instead set `mcpCatalogKv: { namespace, scope }` with a stable deployment-owned scope, never one derived from a request.
|
|
151
|
+
- Pass `reservedHttpPathPrefixes` (the exported constants plus `auth.basePath`) to `createMantleRuntimeRef` so a manifest HTTP Trigger cannot claim a Core path.
|
|
152
|
+
- If you mount public routes, provide a stable `cacheScope`, pass `onPublicChange` to `createMantleRuntimeRef`, and purge the resulting scoped tag there with the Workers cache API. The facade wires this purge itself; a bare `createMantleRuntimeRef` does not.
|
|
153
|
+
- Do not replace Auth, MCP or cache handling with local copies.
|
|
154
|
+
|
|
155
|
+
## Adding an application queue producer
|
|
156
|
+
|
|
157
|
+
The audit queue above is the pattern: declare the binding in `Env` and `wrangler.jsonc`, send from `ctx.waitUntil` after the response is decided, and keep the message contract application-owned. Consume it in the same Worker by switching on `batch.queue`; see [Deferred hooks with Queues](./deferred-hooks-queues.md#multiplexing-application-queues). For the runtime pipeline these pieces sit on, see [Runtime and adapters](../concepts/runtime-and-adapters.md).
|
|
158
|
+
|
|
159
|
+
## Source
|
|
160
|
+
- [`docs/cloudflare-low-level-composition.md`](../../../docs/cloudflare-low-level-composition.md)
|
|
161
|
+
- [`packages/adapters/cloudflare/README.md`](../../../packages/adapters/cloudflare/README.md)
|
|
162
|
+
- [`packages/adapters/cloudflare/src/worker/createMantleWorker.ts`](../../../packages/adapters/cloudflare/src/worker/createMantleWorker.ts)
|
|
163
|
+
- [`packages/adapters/cloudflare/src/mount/bootRuntimeOnce.ts`](../../../packages/adapters/cloudflare/src/mount/bootRuntimeOnce.ts)
|
|
164
|
+
- [`packages/adapters/cloudflare/src/mount/cmsConfig.ts`](../../../packages/adapters/cloudflare/src/mount/cmsConfig.ts)
|
|
165
|
+
- [`packages/adapters/cloudflare/src/oauth/cachePolicy.ts`](../../../packages/adapters/cloudflare/src/oauth/cachePolicy.ts)
|
|
166
|
+
- [`packages/adapters/cloudflare/src/index.ts`](../../../packages/adapters/cloudflare/src/index.ts)
|
|
167
|
+
- [`packages/adapters/cloudflare/src/bindings/index.ts`](../../../packages/adapters/cloudflare/src/bindings/index.ts)
|
|
168
|
+
- [`packages/adapters/cloudflare/src/mount/index.ts`](../../../packages/adapters/cloudflare/src/mount/index.ts)
|
|
169
|
+
- [`packages/adapters/cloudflare/src/worker/index.ts`](../../../packages/adapters/cloudflare/src/worker/index.ts)
|