@aotter/mantle 0.2.0-alpha.7 → 0.2.0-alpha.8
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 +10 -5
- package/dist/auth/caller.js +2 -1
- package/dist/auth/identity.d.ts +2 -2
- package/dist/auth/oauth.d.ts +33 -1
- package/dist/auth/oauth.js +121 -28
- package/dist/auth/routes.js +3 -2
- package/dist/build/compiler.d.ts +44 -122
- package/dist/build/compiler.js +202 -277
- package/dist/build/spec/domain/model/ManifestGrammar.d.ts +6 -5
- package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
- package/dist/build/spec/domain/service/PlanFingerprint.d.ts +2 -0
- package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
- package/dist/domain/describe/{chunk-ZIQVLA7X.js → chunk-ZVXBGP7Y.js} +69 -8
- package/dist/domain/describe/index.js +1 -1
- package/dist/domain/describe/target-hash.js +1 -1
- package/dist/domain/describe/targets.d.ts +1 -1
- package/dist/domain/generated-tools.js +2 -0
- package/dist/domain/paging.js +41 -15
- package/dist/infrastructure/bundle.js +105 -15
- package/dist/infrastructure/cli.js +11 -15
- package/dist/infrastructure/generate.d.ts +1 -3
- package/dist/infrastructure/generate.js +2 -2
- package/dist/infrastructure/generateAuth.js +6 -0
- package/dist/infrastructure/inputs.js +8 -3
- package/dist/infrastructure/wrangler.js +35 -12
- package/dist/kernel/drain.d.ts +5 -0
- package/dist/kernel/drain.js +35 -0
- package/dist/kernel/driver/postgres.js +3 -0
- package/dist/kernel/host-caller.d.ts +1 -0
- package/dist/kernel/host-caller.js +1 -0
- package/dist/kernel/mcp-apps.d.ts +29 -9
- package/dist/kernel/mcp-apps.js +2 -2
- package/dist/kernel/mcp-handler.js +100 -17
- package/dist/kernel/mcp.d.ts +7 -2
- package/dist/kernel/mcp.js +29 -5
- package/dist/kernel/rest-router.js +3 -2
- package/dist/kernel/site.d.ts +1 -3
- package/dist/kernel/site.js +2 -2
- package/dist/kernel/web.d.ts +3 -1
- package/dist/kernel/web.js +3 -0
- package/dist/usecase/adminBindings.js +2 -3
- package/dist/usecase/adminClient.js +87 -0
- package/dist/usecase/adminDescriptors.js +19 -65
- package/dist/usecase/generate.js +5 -4
- package/dist/usecase/mcp-media.js +2 -2
- package/dist/usecase/mcp.js +16 -12
- package/dist/usecase/paging.js +2 -3
- package/dist/usecase/publishing.js +2 -2
- package/dist/usecase/tool-version.js +9 -0
- package/dist/usecase/translations.js +1 -1
- package/dist/usecase/validation.js +120 -19
- package/dist/usecase/web.js +3 -2
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +39 -1
- package/docs/examples/app-shell/package.json +14 -6
- package/docs/examples/app-shell/smoke-kit.mjs +54 -0
- package/docs/examples/app-shell/src/auth.ts +4 -2
- package/docs/examples/app-shell/src/service.ts +2 -2
- package/docs/examples/app-shell/tsconfig.json +4 -1
- package/docs/examples/app-shell/worker-configuration.d.ts +2 -2
- package/docs/examples/app-shell/wrangler.jsonc +5 -4
- package/docs/examples/generated-web/README.md +6 -2
- package/docs/examples/generated-web/wrangler.jsonc +1 -0
- package/docs/examples/purchasing/README.md +1 -1
- package/docs/examples/purchasing/manifests/purchasing.yaml +1 -1
- package/docs/examples/purchasing/smoke.mjs +10 -4
- package/docs/examples/purchasing/src/admin.tsx +13 -0
- package/docs/examples/purchasing/wrangler.jsonc +5 -4
- package/docs/examples/recipes/wrangler.jsonc +5 -4
- package/docs/handbook/cloudflare/authentication.md +22 -2
- package/docs/handbook/cloudflare/deploy-and-operate.md +21 -0
- package/docs/handbook/cloudflare/service-entry.md +55 -28
- package/docs/handbook/concepts/authorization.md +1 -1
- package/docs/handbook/concepts/mcp-and-agents.md +19 -0
- package/docs/handbook/examples/hub.md +1 -1
- package/docs/handbook/guides/admin-ui.md +107 -1
- package/docs/handbook/guides/agent-setup.md +27 -9
- package/docs/handbook/guides/mcp-apps.md +445 -6
- package/docs/handbook/guides/native-admin.md +296 -51
- package/docs/handbook/guides/oauth.md +61 -5
- package/docs/handbook/guides/public-pages.md +2 -2
- package/docs/handbook/guides/size-and-startup.md +1 -1
- package/docs/handbook/navigation.json +0 -4
- package/docs/handbook/reference/admin-descriptors.md +0 -35
- package/docs/handbook/reference/procedure.md +5 -1
- package/docs/handbook/reference/schema.md +13 -3
- package/docs/handbook/reference/surface.md +123 -13
- package/docs/handbook/reference/trigger.md +34 -1
- package/docs/handbook/reference/view.md +1 -1
- package/docs/handbook/releases/index.md +74 -2
- package/docs/handbook/start/overview.md +1 -1
- package/docs/handbook/start/project-and-cli.md +8 -8
- package/docs/handbook/start/quickstart-worker.md +1 -1
- package/docs/skills/develop/SKILL.md +4 -3
- package/docs/skills/integrate/SKILL.md +1 -1
- package/docs/skills/media-gc/SKILL.md +7 -5
- package/docs/skills/provision/SKILL.md +4 -1
- package/docs/skills/update/SKILL.md +1 -1
- package/docs/upgrade-0.1-to-0.2.md +6 -6
- package/package.json +2 -1
- package/skills/README.md +11 -16
- package/skills/mantle/SKILL.md +4 -4
- package/skills/mantle/references/local-testing.md +4 -0
- package/skills/mantle/references/web.md +1 -1
- package/skills/mantle/references/website.md +24 -22
- package/docs/examples/app-shell/src/admin.ts +0 -10
- package/docs/examples/purchasing/src/admin.ts +0 -12
- package/docs/handbook/guides/admin-extensions.md +0 -20
|
@@ -12,8 +12,12 @@ A ref handler is application code called as `(ctx, input)`. Generation prints re
|
|
|
12
12
|
|
|
13
13
|
`target: { schema, id, version? }` binds an Admin operation to a row. SQL targets can be inferred for a single locked row operation; declare a target for a ref operation when needed. Input fields must have the required shapes.
|
|
14
14
|
|
|
15
|
+
## MCP tool hints
|
|
16
|
+
|
|
17
|
+
Every generated AI-facing tool (MCP, WebMCP, publishing and media tools) carries a `title`, `readOnlyHint` and `destructiveHint`; clients such as Claude use them to decide between auto-approval and confirmation. The title is the declared `title`, else the name made readable (`add-item` reads `Add item`). A View is `readOnlyHint: true, destructiveHint: false`. A Procedure is `readOnlyHint: false`, and `destructiveHint` is `false` only when its SQL inserts without overwriting: any DELETE or UPDATE, an `ON CONFLICT DO UPDATE` or a ref handler makes it `true`. Of the generated publishing transitions only `archive` is destructive. The media tools always carry `openWorldHint: true`; the upload policy, list and get tools are read-only and only `delete_media_asset` is destructive. Admin's WebMCP navigation tools (`admin_get_context`, `admin_navigate`) are read-only. `spec.mcp` accepts `readOnlyHint`, `destructiveHint` and `openWorldHint`, and a declaration always wins; a declared `readOnlyHint: true` defaults `destructiveHint` to `false`. Outside the media tools `openWorldHint` appears only when declared. `idempotentHint` is never generated, and `spec.mcp` refuses it. Hints never change authorization.
|
|
18
|
+
|
|
15
19
|
Generated requires checks gate calls. Boundary input validators are generated separately; direct queries accept driver-ready, app-validated input. Dynamic guards and lifecycle hooks are rejected. See [native execution](../guides/native-execution.md) and [surface contracts](surface.md).
|
|
16
20
|
|
|
17
21
|
SQL INSERT/upsert into a user-field scoped Schema is system-only. For a verified user, insert through `ctx.store` so membership and single-partition autofill run before the native write. `ctx.store` speaks storage names, not the manifest's: the handler's `input` uses authored names (`input.parentId`), while Store writes, filters and rows use the folded column (`insert({ parentid: input.parentId })`, `where: { parentid }`, `row.parentid`); generated Store types show the exact names ([typed queries](../guides/typed-queries.md)). SET cannot change a field-scope column, including ON CONFLICT DO UPDATE. Scoped unique conflict targets gain the owner/field prefix automatically.
|
|
18
22
|
|
|
19
|
-
Triggers are the only way request data reaches a Procedure: REST and MCP validate the input against the Procedure's schema, then call it. App code may also call a generated Procedure directly (`import run from "../generated/procedures/<name>.js"`, `await run(db, caller, input)`); `requires`, row policy and checks still apply, but input validation does not, so pass only data the app produced or has verified (a cron job, a callback after its signature check). A page that takes user input submits it to the Procedure's REST Trigger; see `docs/examples/purchasing/src/requests-page.ts`.
|
|
23
|
+
Triggers are the only way request data reaches a Procedure: REST and MCP validate the input against the Procedure's schema, then call it. App code may also call a generated Procedure directly (`import run from "../generated/procedures/<name>.js"`, `await run(db, caller, input)`); `requires`, row policy and checks still apply, but input validation does not, so pass only data the app produced or has verified (a cron job, a callback after its signature check). A page that takes user input submits it to the Procedure's REST Trigger; see `node_modules/@aotter/mantle/docs/examples/purchasing/src/requests-page.ts`.
|
|
@@ -54,7 +54,17 @@ Owner and field columns prefix every unique index in that order. User reads/writ
|
|
|
54
54
|
|
|
55
55
|
## Indexes, checks and search
|
|
56
56
|
|
|
57
|
-
Indexes are ordered composite scalar/native columns. A paged View's `ORDER BY` must match an index on the base Schema after any fixed equality prefix, ending with `id` as the tiebreak: `ORDER BY
|
|
57
|
+
Indexes are ordered composite scalar/native columns. A paged View's `ORDER BY` must match an index on the base Schema after any fixed equality prefix ([index prefixes](#index-prefixes-for-paged-views)), ending with `id` as the tiebreak: `ORDER BY created_at DESC, id DESC` needs `indexes: [[createdAt, id]]` (generation reports `Missing index for ORDER BY ...` otherwise). Required operational fields are NOT NULL; publishing drafts may be incomplete. Store sortable types use indexed/non-null columns, with explicit nullable published_at support.
|
|
58
|
+
|
|
59
|
+
### Index prefixes for paged Views
|
|
60
|
+
|
|
61
|
+
Only equality fixes an index prefix: `col = input.x`, `col = 'literal'` and an `= auth.uid()` scope or restrict. A user-field term (`region IN auth.user('region')`, as a `scope` or `restrict`) binds a set of values, so it never fixes a prefix, even when the caller holds one value or `{ all: true }`: the statement is prepared for a set, and an `ORDER BY` after that column would read and sort all of the caller's rows on every page. With `scope: "region IN auth.user('region')"`, `indexes: [[region, orderNo, id]]` and `ORDER BY orderNo`, generation refuses the View, names the bounded order and links here. The bounded ways:
|
|
62
|
+
|
|
63
|
+
- `ORDER BY region, orderNo`, served by the same index: rows come back grouped by region. `region DESC, orderNo DESC` reads it backwards; on PostgreSQL confirm with `EXPLAIN` that no Sort remains. Every column added to the `ORDER BY` must be non-null and nameable in SQL; a `publishing` Schema field other than a user-field scope column can be null, and the `scope` owner column cannot be named, so the refusal then says this order is unavailable.
|
|
64
|
+
- A View input bound with equality, `WHERE region = input.region ORDER BY orderNo`: the input fixes the prefix, and the scope still applies. Every set-bound column before the ordered ones needs its own equality.
|
|
65
|
+
- For `{ all: true }` callers, an index on the equality-fixed columns, then the ordered columns, then `id`: `[orderNo, id]` here, `[tier, orderNo, id]` with `WHERE tier = input.tier`. It is not bounded for scoped callers, who scan the index and filter, and it does not help a View with any `restrict`, which [`{ all: true }` never lifts](view.md#restrict).
|
|
66
|
+
|
|
67
|
+
Declaring both indexes does not make one View bounded for every caller. System (trusted) reads drop scope terms, so a scope-first index such as `[owner, at]` is not a proved range scan for them.
|
|
58
68
|
|
|
59
69
|
`uniqueIndexes` accept declared fields only, never native entry columns such as `authorId`. For per-member uniqueness, declare a required string `member` field, write `member: caller.subject` in a handler, and use `uniqueIndexes: [[member, orderNumber]]`; do not take `member` from client input.
|
|
60
70
|
|
|
@@ -68,9 +78,9 @@ The runtime never creates, alters or checks tables. Initial DDL is for an empty
|
|
|
68
78
|
|
|
69
79
|
A property marked `readOnly: true` in the JSON Schema is server-maintained (a counter, a total): Admin's generic create stores its `default` whatever the input says, Admin edits ignore it, and only Procedures (or server code through the Store) change it afterwards. JSON Schema defaults are applied by Admin and the Store, not by the database: a SQL Procedure that inserts the row sets the field itself. A root `readOnly: true` makes the whole Schema read-only in Admin.
|
|
70
80
|
|
|
71
|
-
`fields.<name>.widget
|
|
81
|
+
`fields.<name>.widget` selects a built-in control (`textarea`); `list.primaryField`, `list.columns` and enum `filterField` select list presentation; `nav.parentField` chooses the parent a child folds under (required when two or more required `id` references exist) and `nav.standalone` lists the child in the sidebar; `nav.group/order/hidden` place it in the sidebar ([sidebar](../guides/native-admin.md#sidebar)). `list.columns` must not repeat `list.primaryField` or itself (`SCHEMA_UI_INVALID`). Generation validates these against declared fields/indexes. Components that render a field, cell or record aside are not manifest references: they live in `defineAdmin` ([customising Admin](../guides/native-admin.md#customising-admin)), and a manifest that still names one (`widget: <extension>/<contribution>`, `widget` `options`, `list.cells`, `panels`) is refused. JSON Schema root `schema.readOnly: true` disables generic Admin writes, not declared Procedures; `uiSchema.readOnly` is rejected with `SCHEMA_UI_INVALID`. `x-mantle-ref` relates a field to another Schema id or single-field unique field.
|
|
72
82
|
|
|
73
|
-
See [Admin](../guides/admin-ui.md)
|
|
83
|
+
See [Admin](../guides/admin-ui.md) and [diagnostics](diagnostics.md).
|
|
74
84
|
|
|
75
85
|
### Field scope
|
|
76
86
|
|
|
@@ -30,16 +30,36 @@ The generated `validation/views/<name>.ts` and
|
|
|
30
30
|
also export `query(URLSearchParams)` returning `{ input, limit?, cursor? }`.
|
|
31
31
|
REST and MCP use the same generated checks, with no runtime schema interpreter.
|
|
32
32
|
Supported checks are types/unions and nullable, required/properties/additionalProperties,
|
|
33
|
-
arrays/items and length bounds, enum, local `$defs`/`$ref`, string length/pattern,
|
|
34
|
-
numeric bounds/`multipleOf`, and date
|
|
33
|
+
arrays/items and length bounds, enum, `const`, local `$defs`/`$ref`, string length/pattern,
|
|
34
|
+
numeric bounds/`multipleOf`, and the formats `date`, `date-time`, `email` and `uri`. The deliberately permissive
|
|
35
35
|
email check requires exactly one `@`, non-empty local and domain parts, and no
|
|
36
36
|
whitespace; it does not require a dot, verify deliverability, or implement full
|
|
37
|
-
RFC mailbox syntax.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
and formats. Defaults remain
|
|
37
|
+
RFC mailbox syntax. `uri` means an absolute `http:` or `https:` URL, stricter than
|
|
38
|
+
JSON Schema's any-scheme URI, so `javascript:` and `data:` links fail. Integers must be safe; fractional
|
|
39
|
+
`multipleOf` uses a 1e-9 quotient tolerance. Defaults remain
|
|
41
40
|
annotations and are not inserted into request input.
|
|
42
41
|
|
|
42
|
+
View and Procedure input objects (not Schemas or outputs) also take three rules
|
|
43
|
+
between their declared properties, each failing with one `INPUT_VALIDATION_FAILED`
|
|
44
|
+
error whose message names the rule:
|
|
45
|
+
|
|
46
|
+
- `dependentRequired: { weight: [unit] }`: "'unit' is required when 'weight' is
|
|
47
|
+
present.", at the missing field.
|
|
48
|
+
- `anyOf` whose every branch is exactly `{ required: [...] }`: "At least one of
|
|
49
|
+
'weight', 'heightCm' is required.", at the object.
|
|
50
|
+
- `if: { properties: { undo: { const: true } }, required: [undo] }` with `then`
|
|
51
|
+
and/or `else` each exactly `{ required: [...] }`: "'item' is required unless
|
|
52
|
+
'undo' is true.", at the missing field. `if` must list its field in `required`:
|
|
53
|
+
otherwise an absent field would satisfy it.
|
|
54
|
+
|
|
55
|
+
Any other `anyOf`/`if`/`then`/`else`, `oneOf`, `uniqueItems`, object property-count
|
|
56
|
+
bounds and other keywords and formats are refused at generation. The rules only
|
|
57
|
+
validate: types, SQL binds and Admin forms keep the declared fields, and Admin
|
|
58
|
+
shows the server's error at its field. `describe` keeps them as written; MCP and
|
|
59
|
+
WebMCP tools advertise the schema without them and append each rule's sentence to
|
|
60
|
+
the tool description, since some model APIs refuse these keywords in a tool's
|
|
61
|
+
input schema. [Trigger](trigger.md#input-rules) has an example.
|
|
62
|
+
|
|
43
63
|
Native `paged/views/<name>.ts` has a default export, callable as
|
|
44
64
|
`page(db, caller, input, { limit?, cursor? })` after importing it as `page`,
|
|
45
65
|
answering `{ rows, nextCursor? }`.
|
|
@@ -56,10 +76,18 @@ their contracts. Native integer bigint cursor keys are serialized separately.
|
|
|
56
76
|
REST/MCP use separate `paged/wire/views/<name>.ts` SQL when declared integers
|
|
57
77
|
require exact transport projections. Web pages and internal paged Views keep
|
|
58
78
|
driver-native rows. Internal paging creates no public route/tool.
|
|
59
|
-
Authored SQL `LIMIT`
|
|
79
|
+
Authored SQL `LIMIT` bounds the complete ordered row set, including across cursor pages.
|
|
80
|
+
Use `limit` in the paging options to choose each page size (up to 500); it does not expand the View's declared result set.
|
|
81
|
+
An MCP View page keeps the `limit` of 1 to 500 but holds only as many of those rows
|
|
82
|
+
as fit 60,000 characters of JSON, so a result stays under host truncation limits. It
|
|
83
|
+
is cut before the first row that overflows and its next cursor resumes at that row.
|
|
84
|
+
A single row over the budget is returned alone with the cursor, and a View without an
|
|
85
|
+
order stays one whole page; a page still over the budget carries a notice that hosts
|
|
86
|
+
may truncate it.
|
|
60
87
|
A pageable Schema View has a complete NOT NULL scalar tuple ending in the
|
|
61
88
|
compiler's `id` tiebreak. Its ordered columns must match an index after fixed
|
|
62
|
-
equality prefixes
|
|
89
|
+
equality prefixes; a user-field `IN` scope or restrict is not one (see
|
|
90
|
+
[index prefixes](schema.md#index-prefixes-for-paged-views)); multiple columns and mixed directions
|
|
63
91
|
retain the authored order. CTE projections preserve the proof; joins must have a
|
|
64
92
|
proved singleton side, or equality on the joined Schema's `id` or single-field
|
|
65
93
|
unique key, so the base id cannot fan out. LEFT JOIN accepts nullable or missing
|
|
@@ -132,7 +160,10 @@ const tags = await site.head(request, env); // HTML string, empty for unknown/mi
|
|
|
132
160
|
```
|
|
133
161
|
|
|
134
162
|
Page parameter names must exactly match the View's declared input properties.
|
|
135
|
-
Page and list Views must return the page parameters; list Views must accept empty input
|
|
163
|
+
Page and list Views must return the page parameters; list Views must accept empty input: no
|
|
164
|
+
`required`, no root `anyOf`, `enum` or `const`, no `else` rule, and a root `type`, when given, that includes `object`.
|
|
165
|
+
These conditions also apply to each schema a root `$ref` chain reaches, and an unresolved or cyclic chain fails them.
|
|
166
|
+
`web()` throws a `TypeError` for any other list View. Markdown
|
|
136
167
|
must name a textual output column. HTML remains application-owned.
|
|
137
168
|
|
|
138
169
|
`/llms.txt` and `/sitemap.xml` traverse each list's `nextCursor`; `.md` and head
|
|
@@ -169,6 +200,19 @@ transport loads on the first matching request. Pass `handlers` when ref
|
|
|
169
200
|
Procedures are declared, as for REST. Admin tools are listed only on the admin
|
|
170
201
|
endpoint, and publishing visibility belongs to the generated SQL.
|
|
171
202
|
|
|
203
|
+
`apps.public` and `apps.admin` accept a `McpAppsSource` or an array mixing static
|
|
204
|
+
contributors with async `({ env, request }) => McpApps` factories. Static resources
|
|
205
|
+
are checked once at construction; factories resolve only after the endpoint's
|
|
206
|
+
origin and caller gates, and their resource identities and ownership are checked
|
|
207
|
+
on each request even when the tool names are unchanged.
|
|
208
|
+
|
|
209
|
+
Discovery is caller-independent by default (ADR-0014); listing a tool grants no
|
|
210
|
+
execution authority. Configure `authorize` to filter `tools/list` and the tool
|
|
211
|
+
names supplied to resource HTML loaders: that mode also excludes tools refused
|
|
212
|
+
by declared identity, scope and `check` requirements. Without `authorize`, the
|
|
213
|
+
complete endpoint catalogue is not a permission-filtered catalogue. Operation
|
|
214
|
+
validation, guards and credential checks still apply to calls.
|
|
215
|
+
|
|
172
216
|
The app resolves bearer tokens with `resolveCaller(request, auth, { audience:
|
|
173
217
|
new URL('/mcp', request.url).href })` (both surfaces use `<origin>/mcp`). The app
|
|
174
218
|
must also mount Better Auth's `/.well-known/oauth-protected-resource/mcp`
|
|
@@ -176,7 +220,8 @@ metadata route. Auth challenges point to that route. Presented bearer credential
|
|
|
176
220
|
need the `mcp` scope; cookie sessions use the same-origin mutation checks.
|
|
177
221
|
|
|
178
222
|
Generation emits `mcp.ts` only for a non-internal View or MCP Trigger. These
|
|
179
|
-
surfaces typecheck without the `@modelcontextprotocol/server`
|
|
223
|
+
surfaces typecheck without the optional `@modelcontextprotocol/server` peer;
|
|
224
|
+
install the exact version declared in the installed SDK's `peerDependencies`.
|
|
180
225
|
The peer loads by dynamic import on the first matching `/mcp` request; if
|
|
181
226
|
missing, that request fails at runtime. Publishing alone does not select MCP;
|
|
182
227
|
on a selected
|
|
@@ -207,15 +252,42 @@ as an explicit option.
|
|
|
207
252
|
### MCP catalogue and callbacks
|
|
208
253
|
|
|
209
254
|
`mcp({ db, caller, handlers?, locale?, instructions?, serverInfo?, requiredScopes?,
|
|
210
|
-
resourceMetadata?, onObservation?, onRefusal? })` keeps the SDK behind its first
|
|
255
|
+
resourceMetadata?, allowedOrigins?, onObservation?, onRefusal? })` keeps the SDK behind its first
|
|
211
256
|
request. Locale defaults to `en`; localized titles/descriptions fall back to
|
|
212
|
-
English, then their first entry.
|
|
213
|
-
their declared MCP annotations. `instructions` is returned by initialize.
|
|
257
|
+
English, then their first entry. Tools carry the [tool hints](procedure.md#mcp-tool-hints). `instructions` is returned by initialize.
|
|
214
258
|
The scope floor defaults to `["mcp"]` for every credential except cookie sessions;
|
|
215
259
|
`requiredScopes: []` disables that floor. An anonymous closed surface challenges
|
|
216
260
|
from initialize, and every call in a batch is checked before any call runs.
|
|
217
261
|
`resourceMetadata` overrides the challenge URL for reverse proxies.
|
|
218
262
|
|
|
263
|
+
Both endpoints answer a request without `Origin`, which is what server-side MCP
|
|
264
|
+
clients send; a bearer credential needs none. A cookie session is ambient authority, so
|
|
265
|
+
`checkOrigin` also guards it: on a request that is not GET, HEAD or OPTIONS, at least one of
|
|
266
|
+
`Origin` and `Sec-Fetch-Site` must be present and every present one must agree (`Origin` equal
|
|
267
|
+
to the request's own origin; `Sec-Fetch-Site` `same-origin` or `none`), else `AUTH_DENIED` at
|
|
268
|
+
`request:origin`. On MCP it runs only for a tool call that executes (not a read-only call from an
|
|
269
|
+
App host), not for initialize or `tools/list`; `allowedOrigins` does not lift it. A present `Origin` marks a browser: it must name a loopback host
|
|
270
|
+
(`localhost`, `127.0.0.1`, `[::1]`, any port) or an `allowedOrigins` entry, else
|
|
271
|
+
403 before authentication on every credential; `Origin: null` is refused. The
|
|
272
|
+
site's own origin is not implied, because a DNS-rebinding page sends an `Origin`
|
|
273
|
+
equal to the `Host` it rebound, so a browser client served from your deployed
|
|
274
|
+
site lists that origin: `allowedOrigins: ["https://app.example"]`. Entries are
|
|
275
|
+
compared by hostname as the official SDK does (not scheme or port); each is a
|
|
276
|
+
full origin (`https://console.example`) or the SDK's bare host
|
|
277
|
+
(`console.example`, `localhost:8787`), and anything else, such as a path, throws
|
|
278
|
+
when `mcp(...)` is configured; so does a wildcard such as `*` or
|
|
279
|
+
`https://*.example.com`, since entries are matched by exact hostname. `Host` is
|
|
280
|
+
not checked. Cookie writes also need the full same-origin check.
|
|
281
|
+
|
|
282
|
+
`/mcp` sends no CORS headers. `allowedOrigins` therefore serves pages on the
|
|
283
|
+
same origin as the endpoint, or clients behind a proxy that forwards their
|
|
284
|
+
`Origin`; a browser client on another origin also needs the app to answer
|
|
285
|
+
preflight and add CORS headers itself. Under `wrangler dev` with `routes` or
|
|
286
|
+
`dev.host`, Miniflare rewrites an `Origin` equal to the request's own origin to
|
|
287
|
+
the route or `dev.host` origin, and a DNS-rebinding request always has that
|
|
288
|
+
shape. Listing that host would admit rebinding against the local server, so keep
|
|
289
|
+
the route and `dev.host` hosts out of `allowedOrigins` in local development.
|
|
290
|
+
|
|
219
291
|
`onObservation` receives request refusals and invocation attempt/completion
|
|
220
292
|
metadata, including duration and outcome. Its Caller and scopes are frozen copies.
|
|
221
293
|
`onRefusal` receives credential/origin refusals. Neither callback receives request
|
|
@@ -232,6 +304,44 @@ with an output schema they omit `structuredContent` to avoid validating an error
|
|
|
232
304
|
against a success schema. Without one they also expose diagnostic structured
|
|
233
305
|
content. MCP adds no prompts or non-UI resources.
|
|
234
306
|
|
|
307
|
+
### Tool versions
|
|
308
|
+
|
|
309
|
+
Each generated tool (MCP, media, publishing and WebMCP) carries `version`, a
|
|
310
|
+
short hash computed by `mantle generate` of the definition as listed: name,
|
|
311
|
+
title, description without the marker, input schema (a View tool's with its
|
|
312
|
+
`limit` and `cursor` paging properties), output schema and annotations. A
|
|
313
|
+
localized title or description is hashed with all its locales. The description
|
|
314
|
+
ends with `(tool version <version>)`, so an unchanged tool keeps a
|
|
315
|
+
byte-identical description across deploys.
|
|
316
|
+
|
|
317
|
+
MCP results of a call that reaches the tool add a second text block,
|
|
318
|
+
`tool version <version>`; failures other than authorization refusals append
|
|
319
|
+
`; input schema <JSON>`, the input schema the tool is listed with.
|
|
320
|
+
`structuredContent` is unchanged. A `tools/call` naming a tool the endpoint
|
|
321
|
+
serves but the caller may not use is answered by the authentication or
|
|
322
|
+
authorization challenge (HTTP 401 or 403) before the SDK's transport and
|
|
323
|
+
request checks, even though that caller's `tools/list` omits the tool. A
|
|
324
|
+
`tools/call` naming a tool not in the endpoint's catalogue, in a batch or not
|
|
325
|
+
and from a host caller too, is answered with JSON-RPC error `-32602` and the
|
|
326
|
+
message
|
|
327
|
+
`No tool named "<name>" exists in the current catalogue (catalogue version <serverInfo.version>).`
|
|
328
|
+
For such a name the SDK's transport and request checks (headers, protocol
|
|
329
|
+
version, JSON-RPC shape) answer first, so a malformed request gets the same
|
|
330
|
+
refusal as one naming a served tool the caller may use.
|
|
331
|
+
Unless `serverInfo.version` is given, the kernel's `mcp()` sets it to
|
|
332
|
+
a hash of the `surface:name:version` of every tool the endpoint serves, before
|
|
333
|
+
per-caller filtering, so `/mcp` and `/mcp/admin` each report their own catalogue
|
|
334
|
+
version, and it changes when a generated tool's definition changes or a tool is
|
|
335
|
+
added or removed, however the tools are wired.
|
|
336
|
+
|
|
337
|
+
`POST /admin/api/webmcp/{tool}` returns `{ output, version }`. A refusal's
|
|
338
|
+
diagnostic adds `toolVersion`, and `inputSchema` unless it is an authorization
|
|
339
|
+
refusal; internal errors and refusals before the call carry neither. Admin's
|
|
340
|
+
WebMCP results repeat these as the same text block MCP adds. The navigation
|
|
341
|
+
tools `admin_get_context` and `admin_navigate` have no version. Tools an
|
|
342
|
+
application adds itself carry a version only if it sets one. Why: [MCP and
|
|
343
|
+
agents](../concepts/mcp-and-agents.md#when-a-host-shows-old-tools).
|
|
344
|
+
|
|
235
345
|
### Declared View scalars on the wire
|
|
236
346
|
|
|
237
347
|
REST and MCP View pages expose declared JSON columns as JSON values and declared
|
|
@@ -28,8 +28,41 @@ spec:
|
|
|
28
28
|
|
|
29
29
|
The target names a declared Procedure. `http` sources use POST/PUT/PATCH/DELETE and an `/api/` path. Path parameters bind to corresponding declared input fields and override JSON-body values. The generated REST module retains the Procedure's authorization and validates boundary input.
|
|
30
30
|
|
|
31
|
-
An MCP source is `{ kind: mcp, surface: public }` or admin. The selected MCP module exposes the Procedure as a snake-case tool with its
|
|
31
|
+
An MCP source is `{ kind: mcp, surface: public }` or admin. The selected MCP module exposes the Procedure as a snake-case tool with its description, [tool hints](procedure.md#mcp-tool-hints) and requires checks.
|
|
32
32
|
|
|
33
33
|
Lifecycle and schedule sources are rejected at generation with `GENERATE_UNSUPPORTED`, including disabled schedules. Use an app-owned handler or scheduler. For signed webhooks, verify the original raw Request in the app and call a generated Procedure with validated input; do not route it through a JSON boundary before signature verification.
|
|
34
34
|
|
|
35
|
+
## Input rules
|
|
36
|
+
|
|
37
|
+
The admin MCP tool below lists `reason` and `closed` without the rules, and its
|
|
38
|
+
description ends "'reason' is required when 'closed' is false."; a call with
|
|
39
|
+
`closed: false` and no `reason` fails with `INPUT_VALIDATION_FAILED` at
|
|
40
|
+
`procedures/set-ticket/input/reason` (the error path is
|
|
41
|
+
`procedures/<name>/input/<field>`). See [surface contracts](surface.md) for the
|
|
42
|
+
supported rules.
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
46
|
+
kind: Procedure
|
|
47
|
+
metadata: { name: set-ticket }
|
|
48
|
+
spec:
|
|
49
|
+
description: Open or close a ticket.
|
|
50
|
+
input:
|
|
51
|
+
type: object
|
|
52
|
+
required: [id, closed]
|
|
53
|
+
properties: { id: { type: string }, closed: { type: boolean }, reason: { type: string, maxLength: 200 } }
|
|
54
|
+
additionalProperties: false
|
|
55
|
+
if: { properties: { closed: { const: false } }, required: [closed] }
|
|
56
|
+
then: { required: [reason] }
|
|
57
|
+
output: { type: object }
|
|
58
|
+
handler: { sql: 'UPDATE tickets SET closed=input.closed WHERE id=input.id RETURNING id,closed' }
|
|
59
|
+
---
|
|
60
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
61
|
+
kind: Trigger
|
|
62
|
+
metadata: { name: set-ticket-mcp }
|
|
63
|
+
spec:
|
|
64
|
+
source: { kind: mcp, surface: admin }
|
|
65
|
+
target: { procedure: set-ticket }
|
|
66
|
+
```
|
|
67
|
+
|
|
35
68
|
See [surfaces](surface.md) for mounted paths, error mapping and origin checks.
|
|
@@ -29,7 +29,7 @@ Store keeps `find`/`first` search and `count`. `count` scans every matching row;
|
|
|
29
29
|
|
|
30
30
|
Each SELECT output needs a unique static name. Unquoted SQL identifiers fold to lower case; use an explicit quoted alias to preserve case (`r.reqStatus AS "reqStatus"`). Generation warns when an unaliased direct declared field would use its lower-case storage name. Declared-column lineage supplies generated types; computed outputs remain native/unknown. SQL formats declared date-time/date outputs, while computed timestamps retain driver values. See [native execution](../guides/native-execution.md).
|
|
31
31
|
|
|
32
|
-
Bare `views/<encoded-name>.ts` returns row arrays. Proved pageable exposed/internal Views also receive native `paged/views/*`, returning `{ rows, nextCursor? }`. Selected REST/MCP boundaries use separate `paged/wire/views/*` SQL when declared integers require exact transport projections; bare modules also export `WireRow`. Indexed ordering after fixed equality prefixes, non-null typed keys, complete groups and singleton joins/lookups are checked at generation. Unsupported internal paging retains its bare View; exposure requires a supported page contract. Aggregates still scan/group their sources: inspect EXPLAIN.
|
|
32
|
+
Bare `views/<encoded-name>.ts` returns row arrays. Proved pageable exposed/internal Views also receive native `paged/views/*`, returning `{ rows, nextCursor? }`. Selected REST/MCP boundaries use separate `paged/wire/views/*` SQL when declared integers require exact transport projections; bare modules also export `WireRow`. Indexed ordering after fixed equality prefixes ([index prefixes](schema.md#index-prefixes-for-paged-views)), non-null typed keys, complete groups and singleton joins/lookups are checked at generation. Unsupported internal paging retains its bare View; exposure requires a supported page contract. Aggregates still scan/group their sources: inspect EXPLAIN.
|
|
33
33
|
|
|
34
34
|
Publishing Schema fields may be NULL in drafts even when required, so public View paging cannot order by them. `published_at` is accepted by Store ordering but rejected as a View paging key. Use `ORDER BY id`, or keep the business date in an operational Schema as an indexed, required non-null field.
|
|
35
35
|
|
|
@@ -17,6 +17,78 @@ Pin every package you use to the same exact version and upgrade them together.
|
|
|
17
17
|
|
|
18
18
|
## 0.2.0 — in preparation
|
|
19
19
|
|
|
20
|
+
### Upgrading to 0.2.0-alpha.8
|
|
21
|
+
|
|
22
|
+
Pin every selected `@aotter/mantle`, `@aotter/mantle-ui` and
|
|
23
|
+
`@aotter/mantle-admin` dependency to `0.2.0-alpha.8`, refresh the lockfile,
|
|
24
|
+
then regenerate and check the application's owned artifacts. Keep existing
|
|
25
|
+
production migrations application-owned; generated initial DDL is for an empty
|
|
26
|
+
database. This alpha includes breaking Admin authoring and paging changes.
|
|
27
|
+
|
|
28
|
+
1. **Build output and host configuration.** `mantle bundle` writes server
|
|
29
|
+
modules to `dist/worker` and static files to `dist/client`, including
|
|
30
|
+
`dist/client/admin`. Rerun `mantle generate` with the application's `--entry`
|
|
31
|
+
and check the resulting Wrangler `main`, `base_dir`, asset directory and
|
|
32
|
+
build command. See [CLI and generated ownership](../start/project-and-cli.md).
|
|
33
|
+
2. **Application-owned React.** Install React and React DOM in the application
|
|
34
|
+
at the same exact version (the examples use `19.3.0`). Admin takes them as
|
|
35
|
+
peers; a mismatched pair fails its build.
|
|
36
|
+
3. **Replace extensions with application composition.** Move to
|
|
37
|
+
`defineAdmin(meta, { nav, pages, home, components, plugins, mcp })` in
|
|
38
|
+
`src/admin.tsx`. Remove `--admin-extensions`, `adminExtensions`,
|
|
39
|
+
`defineAdminExtension`, extension handlers and removed manifest widget,
|
|
40
|
+
`list.cells` and panel references. `navGroups` becomes `nav.groups`.
|
|
41
|
+
Use the [Admin migration table](../guides/native-admin.md#migrating-from-admin-extensions)
|
|
42
|
+
rather than carrying the former registration layer forward.
|
|
43
|
+
4. **Build the MCP App with the application.** The prebuilt
|
|
44
|
+
`@aotter/mantle-admin/mcp-app` / `mantleAppHtml` export is gone. Use
|
|
45
|
+
`adminMcpApps({ catalogue: mcpAppCatalogues.admin })` with the generated
|
|
46
|
+
catalogue; it reads the built asset. Declare CSP once in `defineAdmin`.
|
|
47
|
+
The default resource URI hashes the built HTML, catalogue and metadata;
|
|
48
|
+
custom HTML functions need an explicit immutable URI. Load interactive
|
|
49
|
+
host HTML through authenticated `resources/read`, which embeds its
|
|
50
|
+
catalogue. See [MCP Apps](../guides/mcp-apps.md).
|
|
51
|
+
5. **Run the generated compatibility check.** Run
|
|
52
|
+
`node --test generated/admin-mcp-app.check.mjs` after building. Install
|
|
53
|
+
the optional `playwright-core` peer and a browser, or set
|
|
54
|
+
`MANTLE_TEST_CHROMIUM`; a missing browser fails rather than skips. This
|
|
55
|
+
checks rendering with a synthetic catalogue, not application authorization
|
|
56
|
+
or completion of a Procedure action.
|
|
57
|
+
6. **Keep the host graph safe.** The MCP build rejects `/admin/api` access,
|
|
58
|
+
cookies and undeclared remote loads. Its entry must be readable in Node.
|
|
59
|
+
An optional `src/admin.mcp.tsx` (or `buildAdmin({ mcpEntry })`) isolates
|
|
60
|
+
browser-only integrations; both roots can reuse one host-safe config,
|
|
61
|
+
components and CSS. Without it the whole shared entry graph is checked.
|
|
62
|
+
7. **Distinguish total bounds from page sizes.** Authored SQL `LIMIT` now
|
|
63
|
+
bounds the complete ordered result set across cursor pages, including web
|
|
64
|
+
indexes. Use paging `options.limit` for page size; remove an authored limit
|
|
65
|
+
only if it was incorrectly being used as a page-size setting. MCP accepts
|
|
66
|
+
sizes of 1–500, but a response budget can return fewer rows: append pages
|
|
67
|
+
until `nextCursor` is absent, not until a short page. See
|
|
68
|
+
[View paging](../reference/surface.md).
|
|
69
|
+
8. **Install build dependencies where the build runs.** Wrangler dev/deploy
|
|
70
|
+
runs its configured build command, so that environment needs
|
|
71
|
+
`node_modules`. The resulting built server/assets do not read
|
|
72
|
+
`node_modules` when served. See the
|
|
73
|
+
[application shell](../../examples/app-shell/README.md).
|
|
74
|
+
|
|
75
|
+
Record/collection cells, inputs, record actions, asides and `RecordView` are
|
|
76
|
+
now truthfully browser-only (`surface: "browser"`); remove unreachable MCP
|
|
77
|
+
branches. View cells remain `both`, and pages opt in through `pages[].mcp`.
|
|
78
|
+
A Procedure action's input is distinct from a collection record input: use a
|
|
79
|
+
Procedure target and MCP Trigger with the View's unchanged `id` and observed
|
|
80
|
+
`version` for native row actions. [Shared custom-page headers](../guides/mcp-apps.md#customising-admin-for-mcp-hosts)
|
|
81
|
+
can compose localized title, description and actions on both surfaces; omit the
|
|
82
|
+
option to keep prior behavior, or use `header: false` for an element-owned heading.
|
|
83
|
+
|
|
84
|
+
OAuth issuer verification now follows Better Auth's normalized URL while
|
|
85
|
+
preserving explicit issuer/JWKS settings. Cloudflare OAuth configurations need
|
|
86
|
+
`global_fetch_strictly_public` for public JWKS fetches; generated settings and
|
|
87
|
+
the official examples retain it. Native MCP Apps use the official ext-apps
|
|
88
|
+
client. Browser Admin now displays a localized explicit reload action when a
|
|
89
|
+
lazy page fails, rather than going blank; it does not reload automatically or
|
|
90
|
+
replay writes.
|
|
91
|
+
|
|
20
92
|
### Native SDK migration (alpha.7)
|
|
21
93
|
|
|
22
94
|
Alpha.7 replaces the previous Core runtime and generated service contract.
|
|
@@ -32,7 +104,7 @@ Existing alpha.6 applications require a source migration:
|
|
|
32
104
|
runs DDL at boot. Apply generated `schema.sql` only to an empty database;
|
|
33
105
|
migrate existing data explicitly. PostgreSQL uses Bun.SQL on Bun and
|
|
34
106
|
postgres.js on Workers. See [native execution](../guides/native-execution.md).
|
|
35
|
-
- Install the separate `@aotter/mantle-admin` package when selecting Admin.
|
|
107
|
+
- Install the separate `@aotter/mantle-admin` package when selecting Admin, with `react` and `react-dom` in the application pinned to one identical exact version (Admin no longer installs them).
|
|
36
108
|
Compose its [native bindings](../guides/native-admin.md) in the application.
|
|
37
109
|
Bare queries retain driver-native values; selected REST/MCP/Admin boundaries
|
|
38
110
|
own validation and declared-column wire conversion.
|
|
@@ -40,7 +112,7 @@ Existing alpha.6 applications require a source migration:
|
|
|
40
112
|
application code. Review [authorization and Caller wiring](../concepts/runtime-and-adapters.md)
|
|
41
113
|
and regenerate all owned artifacts before running `generate --check`.
|
|
42
114
|
|
|
43
|
-
The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.
|
|
115
|
+
The [reference Worker](https://github.com/aotter/mantle/tree/v0.2.0-alpha.8/docs/examples/reference-service) and
|
|
44
116
|
[app shell](../../examples/app-shell/) demonstrate the current composition.
|
|
45
117
|
The following earlier-alpha notes are historical; the current handbook and
|
|
46
118
|
the native migration above supersede their runtime and CLI instructions.
|
|
@@ -9,7 +9,7 @@ Mantle compiles a closed v2 manifest into native SQL and typed TypeScript. The a
|
|
|
9
9
|
| --- | --- |
|
|
10
10
|
| `@aotter/mantle` | SDK, private compiler, generator and `mantle` CLI |
|
|
11
11
|
| `@aotter/mantle-ui` | Pure UI kit and generic interaction controller; no SDK dependency |
|
|
12
|
-
| `@aotter/mantle-admin` | Admin server, SPA
|
|
12
|
+
| `@aotter/mantle-admin` | Admin server, app-built SPA library, preview and MCP App; SDK peer |
|
|
13
13
|
|
|
14
14
|
Start with [a Worker](quickstart-worker.md) or [CLI and targets](project-and-cli.md). Existing TypeScript/Node apps can import selected generated operations using their own handle and verified Caller; no router or identity replacement is required. See the packaged [integrate skill](../../skills/integrate/SKILL.md).
|
|
15
15
|
|
|
@@ -10,13 +10,13 @@ description: Generation targets, CLI options and ownership of native application
|
|
|
10
10
|
| `generated/` | Generator: TypeScript, native SQL and selected bindings |
|
|
11
11
|
| `migrations/` | App's agent and native migration tool |
|
|
12
12
|
| `wrangler.jsonc` | App configuration; generator maintains selected split-build keys |
|
|
13
|
-
| `dist/` | Disposable bundle output; do not commit |
|
|
13
|
+
| `dist/` | Disposable bundle output (`dist/worker`, `dist/client`); do not commit |
|
|
14
14
|
|
|
15
15
|
## Commands
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
mantle generate <target> <output-directory> <manifest>... [options]
|
|
19
|
-
mantle bundle <entry> <outdir>
|
|
19
|
+
mantle bundle <entry> <outdir> [--assets <dir>]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
| Target | Dialect | Handle |
|
|
@@ -34,24 +34,24 @@ Other handles may adapt to the same [structural driver contracts](../guides/adap
|
|
|
34
34
|
| `--admin` | Emit native Admin bindings and `admin/*` modules; install the separate Admin package. |
|
|
35
35
|
| `--auth <module>` | Read plain `authOptions(env)` (or an exported auth instance's options) and append selected Better Auth DDL. Repeat it once per Better Auth instance. Prefer plain options without initialization side effects. |
|
|
36
36
|
| `--site <module>` | Read `siteDefaults`; emit site/media bindings and deployment SQL. |
|
|
37
|
-
| `--admin-extensions <module>` | Read the named/default `adminExtensions` array and the optional `navGroups` (sidebar groups; see native-admin.md "Sidebar"). The app shell passes `src/admin.ts`. |
|
|
38
37
|
| `--wrangler <path>` | Existing JSONC/JSON/TOML config; default finds one or creates `wrangler.jsonc`. TOML receives manual-edit instructions. |
|
|
39
38
|
| `--entry <path>` | Worker source entry; default `src/worker.ts`. |
|
|
40
|
-
| `--outdir <path>` |
|
|
39
|
+
| `--outdir <path>` | Bundle output; default `dist`. The Worker goes to `<outdir>/worker`. Keep separate from source/generated files. |
|
|
40
|
+
| `--assets <path>` | With `--admin`: the static assets directory `mantle bundle` builds Admin into (`<assets>/admin`); default `<outdir>/client`. Written to Wrangler's `assets.directory`, and to the build command when not the default. |
|
|
41
41
|
|
|
42
42
|
## Outputs
|
|
43
43
|
|
|
44
|
-
`generated/.mantle-inputs.json` records the Mantle version and a hash of every input the run read (manifests and the
|
|
45
|
-
`--auth
|
|
44
|
+
`generated/.mantle-inputs.json` records the Mantle version, the target, and a hash of every input the run read (manifests and the
|
|
45
|
+
`--auth` and `--site` modules). `mantle bundle`, which Wrangler runs for `wrangler dev` and every
|
|
46
46
|
deploy, refuses to build when an input changed since, so stale generated code cannot run; regenerate and rebuild.
|
|
47
47
|
Commit it with the other generated files.
|
|
48
48
|
The bundle also prints notes, never errors, for SQL in the app's own code against Better Auth's tables (read users
|
|
49
49
|
through `userOf(caller)` or `readIdentity(auth)` instead) and for Better Auth roles beyond `admin` and `user`.
|
|
50
50
|
|
|
51
|
-
`generated.ts` exports typed queries and Store. Direct `views/<encoded-name>.ts` and `procedures/<encoded-name>.ts` entries export an operation and its types. Proved pageable Views also have `paged/views/*`; boundary checks live in `validation/*`. `admin-descriptors.ts`, `rest.ts` and `web.ts` are always emitted. Manifest exposure selects `mcp.ts`; `admin-bindings.ts` and `admin/*` require `--admin`. Auth and site are explicit options.
|
|
51
|
+
`generated.ts` exports typed queries and Store. Direct `views/<encoded-name>.ts` and `procedures/<encoded-name>.ts` entries export an operation and its types. Proved pageable Views also have `paged/views/*`; boundary checks live in `validation/*`. `admin-descriptors.ts`, `rest.ts` and `web.ts` are always emitted. Manifest exposure selects `mcp.ts`; `admin-bindings.ts`, `admin-client.ts` (browser-side types and hooks for `admin.tsx`) and `admin/*` require `--admin`. Auth and site are explicit options.
|
|
52
52
|
|
|
53
53
|
Apply `schema.sql` only to an empty database. Later changes require [explicit migrations](../concepts/runtime-and-adapters.md#schema-installation-and-migrations); generation and runtime do not inspect the database. Ref declarations print the `Handlers` keys to implement.
|
|
54
54
|
|
|
55
55
|
Never edit generated output by hand. Merge manifests/options, regenerate with the same command, then review TypeScript and SQL diffs. Use that exact command with `--check` in CI. Unmarked app files are preserved. Imported option modules must be pure; generation does not make their side effects safe.
|
|
56
56
|
|
|
57
|
-
On Workers, generation configures `mantle bundle` as Wrangler's build command. It emits minified split ESM with a startup guard for eager Better Auth imports. On Bun, the app owns `Bun.serve`, its Database/SQL pool and scheduler. See [service composition](../cloudflare/service-entry.md).
|
|
57
|
+
On Workers, generation configures `mantle bundle` as Wrangler's build command. It emits minified split ESM into `<outdir>/worker` with a startup guard for eager Better Auth imports and for Admin's browser code (`@aotter/mantle-admin/app` and `/hooks`, which `generated/admin-client.ts` imports; they belong to `admin.tsx`). When the Worker imports `@aotter/mantle-admin/server`, it also builds Admin with Vite into `<assets>/admin` (`--assets`, default `<outdir>/client`), from `admin.tsx` beside the Worker entry or Admin's built-in entry; `src/admin.ts` is not an entry. The assets directory must not overlap the Worker directory, and neither the outdir nor the assets directory may be the working directory or contain the Worker entry. The built-in entry is not rebuilt while the installed `@aotter/mantle-admin` keeps its path and version and its direct dependencies and its `react`/`react-dom` peers keep theirs; an `admin.tsx` entry builds every time. The build needs no install scripts (`npm ci --ignore-scripts` works). A `package-lock.json` made on one OS can lack the native build binaries (rolldown, `@tailwindcss/oxide`, lightningcss) that npm installs per platform from `optionalDependencies`, so a lockfile made on macOS can fail the build on a Linux host; create the lockfile on the build platform or use pnpm. `wrangler dev` and `wrangler deploy` run that build command, so they need `node_modules`. The built output (`<outdir>/worker`, `<assets>`) never reads it, so a pipeline that deletes `node_modules` must run `wrangler deploy` first. On Bun, the app owns `Bun.serve`, its Database/SQL pool and scheduler; Bun runs the server as TypeScript, so for a `bun-*` target recorded in `.mantle-inputs.json`, `mantle bundle src/server.ts dist` skips the Worker bundle and builds only Admin, into `dist/client/admin` (read with `bunAdminAssets("dist/client/admin")`, adapted for `adminShell` as in service composition). See [service composition](../cloudflare/service-entry.md).
|
|
@@ -3,7 +3,7 @@ description: Generate a small native D1 service and mount an application-owned W
|
|
|
3
3
|
---
|
|
4
4
|
# Quickstart: a service on Workers
|
|
5
5
|
|
|
6
|
-
Create an application outside the SDK checkout. Install an exact released `@aotter/mantle` version, TypeScript, Wrangler and Cloudflare Worker types. Read that installed version's handbook; this page describes the native 0.2.0 contract.
|
|
6
|
+
Create an application outside the SDK checkout. Install an exact released `@aotter/mantle` version, TypeScript, Wrangler and Cloudflare Worker types; `node_modules/@aotter/mantle/docs/examples/app-shell/package.json` lists the versions this release is tested with. Read that installed version's handbook; this page describes the native 0.2.0 contract.
|
|
7
7
|
|
|
8
8
|
Save `manifests/tickets.yaml`:
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
|
|
|
18
18
|
with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
|
|
19
19
|
all generated output, run the same generation command with `--check`, and keep
|
|
20
20
|
bundle `dist/` untracked. The app owns its verified Caller, handlers and native
|
|
21
|
-
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin`
|
|
21
|
+
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
|
|
22
22
|
|
|
23
23
|
Use the native Store/query/surface entries and app-owned fetch from the service composition guide.
|
|
24
24
|
Keep optional modules out of unused entry imports and auth dynamically loaded.
|
|
@@ -41,8 +41,9 @@ version? }` selects row actions and binds the row id / reviewed OCC version to
|
|
|
41
41
|
the named inputs. Other admin operations appear under Operations in the sidebar.
|
|
42
42
|
Keep state conditions in the SQL WHERE: simple AND equality/IN literals become
|
|
43
43
|
row-menu hints, while the Procedure remains the authority. Required-ref child
|
|
44
|
-
collections fold under their parent's sidebar group;
|
|
45
|
-
`uiSchema.nav.
|
|
44
|
+
collections fold under their parent's sidebar group; with two or more required
|
|
45
|
+
`id` refs, set Schema `uiSchema.nav.parentField` to the parent's ref (generate
|
|
46
|
+
refuses otherwise); use `uiSchema.nav.standalone: true` for a top-level entry.
|
|
46
47
|
|
|
47
48
|
## Database changes
|
|
48
49
|
|
|
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
|
|
|
18
18
|
with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
|
|
19
19
|
all generated output, run the same generation command with `--check`, and keep
|
|
20
20
|
bundle `dist/` untracked. The app owns its verified Caller, handlers and native
|
|
21
|
-
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin`
|
|
21
|
+
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
|
|
22
22
|
|
|
23
23
|
Keep the application router, database/pool, identity and frontend. Replace
|
|
24
24
|
Core consumers explicitly and compose only selected generated entries. Resolve
|
|
@@ -41,11 +41,13 @@ objects approved by the user.
|
|
|
41
41
|
|
|
42
42
|
`POST /admin/api/media/uploads` issues presigned PUT URLs that expire after 15
|
|
43
43
|
minutes, for keys under `uploads/<purpose>/<group>/<role>.<ext>`. Commit
|
|
44
|
-
copies each checked object to
|
|
45
|
-
`committedAt` metadata)
|
|
46
|
-
keys are
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
copies each checked object to `committed/<group>/<attempt>/<role>.<ext>` (with
|
|
45
|
+
`committedAt` metadata); the attempt UUID isolates concurrent commits. Upload
|
|
46
|
+
keys are removed after the database commit succeeds, or when a group is
|
|
47
|
+
rejected. An old upload key may also be leftover cleanup from a successful or
|
|
48
|
+
uncertain commit: it is not evidence that the media row is absent. Once expired,
|
|
49
|
+
it cannot create a new asset. Objects outside `uploads/` are never candidates;
|
|
50
|
+
reconcile attempt copies separately with live database references.
|
|
49
51
|
|
|
50
52
|
## Audit
|
|
51
53
|
|
|
@@ -19,7 +19,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
|
|
|
19
19
|
with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
|
|
20
20
|
all generated output, run the same generation command with `--check`, and keep
|
|
21
21
|
bundle `dist/` untracked. The app owns its verified Caller, handlers and native
|
|
22
|
-
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin`
|
|
22
|
+
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
|
|
23
23
|
|
|
24
24
|
Deploy/create resources only within the user's authorization; reuse configured
|
|
25
25
|
host resources. Apply reviewed and rehearsed migrations before routing traffic;
|
|
@@ -27,6 +27,9 @@ imports/first requests perform no DDL or schema checks. Keep auth dynamic, produ
|
|
|
27
27
|
secrets in the host store, trusted HTTPS origin and verified bootstrap rule,
|
|
28
28
|
production email and selected media wiring. Configure Admin ASSETS worker-first
|
|
29
29
|
as in [Workers assets and secrets](../../handbook/cloudflare/service-entry.md#workers-assets-and-secrets). Hyperdrive caching stays off; PG settings are deployment-owned.
|
|
30
|
+
Keep the Workers [public-fetch flags](../../handbook/cloudflare/deploy-and-operate.md#public-fetch-routing),
|
|
31
|
+
including environment overrides; verify deployed MCP with a real OAuth bearer
|
|
32
|
+
(`initialize` and `tools/list`), not only a browser session cookie.
|
|
30
33
|
Bun uses native Database/SQL, an app server and scheduler. Verify admin sign-in
|
|
31
34
|
(role admin), denied access, public reads, a persisted write, MCP and selected UI.
|
|
32
35
|
Report artifact/resources/checks and keep rollback artifacts.
|
|
@@ -18,7 +18,7 @@ Follow the installed [native generation and service workflow](../../handbook/clo
|
|
|
18
18
|
with its [native contract](../../handbook/guides/native-execution.md) and [supported declarations](../../handbook/reference/features.md). Commit
|
|
19
19
|
all generated output, run the same generation command with `--check`, and keep
|
|
20
20
|
bundle `dist/` untracked. The app owns its verified Caller, handlers and native
|
|
21
|
-
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin`
|
|
21
|
+
resources; Better Auth’s admin plugin `adminRoles` defines the admin layer (default `["admin"]`); `ctx.admin` takes no role list ([authorization](../../handbook/cloudflare/authentication.md)).
|
|
22
22
|
|
|
23
23
|
Pin exact target SDK/peer versions and read its release notes and Admin asset
|
|
24
24
|
installation instructions. Core-to-native replaces app imports/wiring; it is not
|
|
@@ -13,12 +13,12 @@ The remainder describes the earlier alpha contract for historical reference.
|
|
|
13
13
|
|
|
14
14
|
No codemod, and no in-place upgrade of a 0.1.x database. A coding agent moves
|
|
15
15
|
one project by hand with this guide. When it and
|
|
16
|
-
[ADR-0032](https://github.com/aotter/mantle/blob/
|
|
17
|
-
[ADR-0033](https://github.com/aotter/mantle/blob/
|
|
18
|
-
[ADR-0034](https://github.com/aotter/mantle/blob/
|
|
19
|
-
[ADR-0035](https://github.com/aotter/mantle/blob/
|
|
20
|
-
[ADR-0036](https://github.com/aotter/mantle/blob/
|
|
21
|
-
[ADR-0038](https://github.com/aotter/mantle/blob/
|
|
16
|
+
[ADR-0032](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0032-store-centric-core-and-service-contract.md),
|
|
17
|
+
[ADR-0033](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0033-storage-converges-to-the-plan.md),
|
|
18
|
+
[ADR-0034](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0034-store-is-authored-as-sql.md),
|
|
19
|
+
[ADR-0035](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0035-sql-dialects.md),
|
|
20
|
+
[ADR-0036](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0036-host-and-dialect.md) or
|
|
21
|
+
[ADR-0038](https://github.com/aotter/mantle/blob/v0.2.0-alpha.7/docs/adr/0038-bun-native-host.md) disagree, the ADR wins.
|
|
22
22
|
|
|
23
23
|
## Rules
|
|
24
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aotter/mantle",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"engines": {
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
+
"@modelcontextprotocol/client": "2.1.0",
|
|
95
96
|
"@modelcontextprotocol/server": "2.2.0",
|
|
96
97
|
"@better-auth/core": "1.7.7",
|
|
97
98
|
"@better-auth/oauth-provider": "1.7.7",
|