@aotter/mantle 0.2.0-alpha.7 → 0.2.0-alpha.9
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 +27 -5
- package/dist/auth/adapter.js +9 -6
- 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 +6 -4
- 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 +118 -15
- package/dist/infrastructure/cli.js +11 -15
- package/dist/infrastructure/generate.d.ts +1 -3
- package/dist/infrastructure/generate.js +11 -3
- package/dist/infrastructure/generateAuth.js +57 -0
- package/dist/infrastructure/inputs.js +8 -3
- package/dist/infrastructure/wrangler.js +50 -12
- package/dist/kernel/binds.d.ts +0 -3
- package/dist/kernel/contracts.d.ts +1 -1
- package/dist/kernel/contracts.js +4 -4
- package/dist/kernel/dialect/common.d.ts +0 -1
- package/dist/kernel/drain.d.ts +5 -0
- package/dist/kernel/drain.js +35 -0
- package/dist/kernel/driver/postgres.js +6 -2
- 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 +3 -3
- package/dist/kernel/mcp-handler.js +101 -18
- package/dist/kernel/mcp.d.ts +12 -3
- package/dist/kernel/mcp.js +31 -6
- package/dist/kernel/rest-router.js +3 -2
- package/dist/kernel/scalars.js +3 -2
- package/dist/kernel/site.d.ts +1 -3
- package/dist/kernel/site.js +2 -2
- package/dist/kernel/web-index.js +40 -0
- package/dist/kernel/web.d.ts +3 -1
- package/dist/kernel/web.js +6 -37
- 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 +3 -3
- package/dist/usecase/site.js +0 -1
- package/dist/usecase/tool-version.js +9 -0
- package/dist/usecase/translations.js +1 -1
- package/dist/usecase/validation.js +128 -19
- package/dist/usecase/web.js +3 -2
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +45 -3
- 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 +6 -3
- package/docs/examples/app-shell/src/service.ts +3 -3
- 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/club-events/manifests/club-events.yaml +7 -0
- package/docs/examples/club-events/src/handlers.ts +1 -1
- 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 +17 -1
- package/docs/examples/purchasing/smoke.mjs +10 -4
- package/docs/examples/purchasing/src/admin.tsx +13 -0
- package/docs/examples/purchasing/src/handlers.ts +1 -1
- package/docs/examples/purchasing/wrangler.jsonc +5 -4
- package/docs/examples/recipes/wrangler.jsonc +5 -4
- package/docs/handbook/cloudflare/authentication.md +43 -6
- package/docs/handbook/cloudflare/chatgpt-sites.md +4 -2
- package/docs/handbook/cloudflare/deploy-and-operate.md +45 -1
- package/docs/handbook/cloudflare/media-r2.md +2 -3
- package/docs/handbook/cloudflare/service-entry.md +68 -31
- package/docs/handbook/concepts/authorization.md +1 -1
- package/docs/handbook/concepts/mcp-and-agents.md +19 -0
- package/docs/handbook/concepts/runtime-and-adapters.md +1 -1
- 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/agent-workflow-acceptance.md +222 -0
- package/docs/handbook/guides/mcp-apps.md +522 -6
- package/docs/handbook/guides/native-admin.md +300 -51
- package/docs/handbook/guides/oauth.md +119 -5
- package/docs/handbook/guides/public-pages.md +2 -2
- package/docs/handbook/guides/size-and-startup.md +1 -1
- package/docs/handbook/guides/typed-queries.md +35 -0
- package/docs/handbook/navigation.json +0 -4
- package/docs/handbook/reference/admin-descriptors.md +0 -35
- package/docs/handbook/reference/procedure.md +6 -2
- package/docs/handbook/reference/schema.md +13 -3
- package/docs/handbook/reference/surface.md +129 -15
- package/docs/handbook/reference/trigger.md +43 -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 +16 -8
- package/docs/handbook/start/quickstart-worker.md +1 -1
- package/docs/skills/develop/SKILL.md +46 -18
- package/docs/skills/integrate/SKILL.md +4 -9
- package/docs/skills/media-gc/SKILL.md +7 -5
- package/docs/skills/provision/SKILL.md +18 -12
- package/docs/skills/update/SKILL.md +19 -18
- 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 +39 -17
- package/skills/mantle/references/local-testing.md +9 -0
- package/skills/mantle/references/web.md +1 -1
- package/skills/mantle/references/website.md +26 -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
- package/docs/skills/develop/mcp-app.md +0 -39
- package/skills/.keep +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The SDK ships kernel, CLI, generator and private compiler together. Request modules do not import compiler code. Import only selected generated operations and bindings; direct operations can omit Store/Admin/auth entirely.
|
|
4
4
|
|
|
5
|
-
Workers use `mantle bundle` to emit split ESM. Keep Better Auth behind `authLoader(() => import(...))`; the startup graph guard rejects eager auth imports. The official MCP SDK and selected Admin operations load on matching requests. Generated module imports perform no SQL or kernel initialization; Store metadata initializes on first use without SQL.
|
|
5
|
+
Workers use `mantle bundle` to emit split ESM. Keep Better Auth behind `authLoader(() => import(...))`; the startup graph guard rejects eager auth imports, and Admin's browser code (`@aotter/mantle-admin/app`, `/hooks`, and so `generated/admin-client.ts`): import those only from `admin.tsx`, which `mantle bundle` builds into the assets directory. The official MCP SDK and selected Admin operations load on matching requests. Generated module imports perform no SQL or kernel initialization; Store metadata initializes on first use without SQL.
|
|
6
6
|
|
|
7
7
|
Contributor checks use tracked fixtures to compare runtime/startup graphs against `size-budgets.json` and inspect the published allowlist in `check:package`. Package packed/unpacked bytes are reported separately from request startup and query costs. Do not infer latency from bundle bytes.
|
|
8
8
|
|
|
@@ -90,3 +90,38 @@ Interactive `transaction(async tx => …)` exists on bun:sqlite and PostgreSQL,
|
|
|
90
90
|
Ref handlers receive `{ caller, db, store }` bound to the same Caller and transaction. Implement the generated `Handlers` interface and pass it to `store(db, caller, { handlers })`. Direct Procedure modules expose SQL Procedures; ref Procedures are reached through the bound Store or selected surfaces.
|
|
91
91
|
|
|
92
92
|
See [native execution](native-execution.md) for values and write outcomes and [migrations](../concepts/runtime-and-adapters.md#schema-installation-and-migrations) before changing a Schema.
|
|
93
|
+
|
|
94
|
+
## Prefer Store-backed ref Procedures for simple writes
|
|
95
|
+
|
|
96
|
+
For a single-entity create or partial update, prefer a declared `ref` Procedure
|
|
97
|
+
whose handler awaits its caller-bound `ctx.store.collection(...).insert(...)`
|
|
98
|
+
or `.update(id, allowedPatch, { lock: expectedVersion })`. Keep the Procedure's
|
|
99
|
+
`requires` and generated validation at its invocation boundary: Store enforces
|
|
100
|
+
row policy, but does not substitute for operation authorization. Do not invoke
|
|
101
|
+
a handler directly from a route and accidentally bypass those checks.
|
|
102
|
+
|
|
103
|
+
Insert/update returns the actual written native row. Inside a transaction,
|
|
104
|
+
that result is provisional until the outer transaction commits; later failure
|
|
105
|
+
can still roll it back. Report committed success or emit external effects only
|
|
106
|
+
after that transaction succeeds. Return an explicit
|
|
107
|
+
allowlist of useful business fields, its backend `id` and returned `version`;
|
|
108
|
+
do not spread the entire row, echo the submitted version, calculate a new
|
|
109
|
+
version instead of reading the result, or add a generic receipt adapter. These
|
|
110
|
+
ref results are app-owned: declare their output schema and make any required
|
|
111
|
+
native-to-wire conversion explicit at the boundary.
|
|
112
|
+
|
|
113
|
+
Patch only permitted fields. Omitted fields remain unchanged; define whether
|
|
114
|
+
explicit null clears a nullable field or is invalid. Do not expand a partial
|
|
115
|
+
request into a full replacement with defaults for unseen fields. A stale lock
|
|
116
|
+
is a conflict; preserve intent and reconcile/review rather than silently
|
|
117
|
+
refreshing the lock and retrying.
|
|
118
|
+
|
|
119
|
+
Use SQL Procedures when conditions or related effects must commit together:
|
|
120
|
+
for example, creating an order while decrementing stock under a CHECK, a state
|
|
121
|
+
transition guarded by an atomic WHERE, or an additive restock upsert. A
|
|
122
|
+
pre-read in TypeScript followed by an unguarded write is not equivalent.
|
|
123
|
+
Store `batch` is atomic for known writes; a D1 batch step cannot consume a prior
|
|
124
|
+
step's returned ID. D1 has no interactive transaction callback, so retain the
|
|
125
|
+
supported SQL program or another existing native atomic boundary for that case.
|
|
126
|
+
The [MCP design audit](agent-workflow-acceptance.md) applies these choices before
|
|
127
|
+
implementation and separates them from independent end-user acceptance.
|
|
@@ -45,44 +45,9 @@ stars, CTEs and subqueries). Translation
|
|
|
45
45
|
metadata and selected execution share the generated translation policy. Child
|
|
46
46
|
fields win; private scope fields cannot be overlaid by inherited parent fields.
|
|
47
47
|
|
|
48
|
-
Pass declarations with `mantle generate --admin-extensions ./admin-extensions.mjs`.
|
|
49
|
-
The module exports a named `adminExtensions` array or a default array; it should
|
|
50
|
-
contain plain declarations, without constructing runtime services:
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
export const adminExtensions = [{ id: 'audit', contributes: {
|
|
54
|
-
fields: [{ id: 'color', target: 'field.input/v1',
|
|
55
|
-
optionsSchema: { type: 'object', properties: { palette: { type: 'string' } } }
|
|
56
|
-
}]
|
|
57
|
-
}}];
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
The programmatic generation options accept the same declarations:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
await generateFromSources({ target, sources,
|
|
64
|
-
adminExtensions: [{ id: 'audit', contributes: {
|
|
65
|
-
fields: [{ id: 'color', target: 'field.input/v1',
|
|
66
|
-
optionsSchema: { type: 'object', properties: { palette: { type: 'string' } } }
|
|
67
|
-
}]
|
|
68
|
-
}}]
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Only `id` and `contributes` are needed at generation. `extensionBindings` records
|
|
73
|
-
Schema/Procedure field widgets, Schema/admin View cells and sidebar panels.
|
|
74
|
-
Unknown contributions, wrong targets and invalid options fail generation with
|
|
75
|
-
`UI_EXTENSION_UNKNOWN`, `UI_EXTENSION_TARGET` or `UI_EXTENSION_OPTIONS`, retaining
|
|
76
|
-
0.2 paths. Serving extension modules and handling extension actions is separate.
|
|
77
|
-
|
|
78
48
|
`adminBindings()` provides the selected adapter and checked entry/View/operation
|
|
79
49
|
bindings. The server shares native read templates across collections; descriptors
|
|
80
50
|
carry compiler-verified policy/field fragments and generated validators, not a
|
|
81
51
|
copy of full SQL for every order/direction. Metadata endpoints perform no storage
|
|
82
52
|
read of their own; caller resolution and optional site/user enrichment may query.
|
|
83
53
|
Statistics capability is advertised only when its native route is mounted.
|
|
84
|
-
|
|
85
|
-
Full extension declarations also emit `extensionMetadata` and, with Admin selected,
|
|
86
|
-
`admin-extensions.ts` with a runtime `extensionBindings` function and compiled
|
|
87
|
-
settings checks. Native modules are host-served; install integrity in the host
|
|
88
|
-
shell import map. `source` modules are refused.
|
|
@@ -4,7 +4,7 @@ A Procedure requires `input`, `output` JSON Schemas and a `handler` containing S
|
|
|
4
4
|
|
|
5
5
|
SQL uses declared Schemas, `input.<name>`, `auth.uid()` and native expressions accepted by the selected compiler dialect. Compiler policy supplies scope and system fields. Do not assign scope, version, status, published_at, timestamps or author columns yourself. Publishing writes use generated transitions. `restrict` narrows the rows the statements read and write, as on a [View](view.md#restrict); it is refused on ref handlers.
|
|
6
6
|
|
|
7
|
-
A row-classified INSERT/UPDATE/DELETE (one that targets a single row, e.g. `WHERE id = input.id`) must affect one row. Use `WHERE id = input.id AND version = input.expectedVersion` for explicit OCC. Set operations, including UPDATE with a subquery and `INSERT ... SELECT`, may affect zero rows without an error: check the returned rows when zero means failure. A unique clash, a failed Schema `checks` constraint (
|
|
7
|
+
A row-classified INSERT/UPDATE/DELETE (one that targets a single row, e.g. `WHERE id = input.id`) must affect one row. Use `WHERE id = input.id AND version = input.expectedVersion` for explicit OCC. Set operations, including UPDATE with a subquery and `INSERT ... SELECT`, may affect zero rows without an error: check the returned rows when zero means failure. A unique clash, a failed Schema `checks` constraint or a failed row assertion is `CONFLICT` (409); statements run atomically. A check failure keeps that code and its existing `A check of the Schema failed` message, and sets `ContractError.fields` to `{ kind: "check", schema, check }`; `check` is the expression on SQLite/D1 and the generated constraint name on PostgreSQL. Results are raw `{ results }`, not runtime-validated authored outputs. RETURNING uses SQL output names; quote an explicit alias to preserve declared case (`RETURNING reqStatus AS "reqStatus"`). Generation warns about unaliased direct fields whose declared name differs from storage.
|
|
8
8
|
|
|
9
9
|
A row-classified UPDATE/DELETE on an excluded or missing id affects zero rows and returns HTTP 409 (`CONFLICT`), just like a stale version; reads return not found. Native Admin's visible-row pre-read returns 404 for excluded/missing ids.
|
|
10
10
|
|
|
@@ -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
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
snake-cased View names on each declared surface, and snake-cased Procedure
|
|
14
14
|
names per MCP Trigger. Its SDK loads only through a request-time dynamic import.
|
|
15
15
|
- HTTP/MCP Triggers declare exposure only. `mantle generate` refuses schedule
|
|
16
|
-
and lifecycle Triggers;
|
|
16
|
+
and lifecycle Triggers; host scheduling is a separate capability.
|
|
17
17
|
- Native Admin retains selected 0.2 paths and the console contracts; see
|
|
18
18
|
[Native Admin](../guides/native-admin.md) for app-mounted
|
|
19
19
|
`adminBindings()` and `entries(bindings.schemas, bindings.adapter)`.
|
|
@@ -25,21 +25,43 @@ challenges, JSON-RPC, web rendering, keyset paging) lives in the package's
|
|
|
25
25
|
`rest`, `mcp`, `web`, `paging` and `validation` kernel modules, so a runtime fix
|
|
26
26
|
ships as a package update instead of a regeneration.
|
|
27
27
|
|
|
28
|
+
Procedure surface selection follows the [HTTP/MCP pairing default and deliberate asymmetry exception](trigger.md); no surface is implied.
|
|
29
|
+
|
|
28
30
|
The generated `validation/views/<name>.ts` and
|
|
29
31
|
`validation/procedures/<name>.ts` export `validate(unknown)`. View validators
|
|
30
32
|
also export `query(URLSearchParams)` returning `{ input, limit?, cursor? }`.
|
|
31
33
|
REST and MCP use the same generated checks, with no runtime schema interpreter.
|
|
32
34
|
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
|
|
35
|
+
arrays/items and length bounds, enum, `const`, local `$defs`/`$ref`, string length/pattern,
|
|
36
|
+
numeric bounds/`multipleOf`, and the formats `date`, `date-time`, `email` and `uri`. The deliberately permissive
|
|
35
37
|
email check requires exactly one `@`, non-empty local and domain parts, and no
|
|
36
38
|
whitespace; it does not require a dot, verify deliverability, or implement full
|
|
37
|
-
RFC mailbox syntax.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
and formats. Defaults remain
|
|
39
|
+
RFC mailbox syntax. `uri` means an absolute `http:` or `https:` URL, stricter than
|
|
40
|
+
JSON Schema's any-scheme URI, so `javascript:` and `data:` links fail. Integers must be safe; fractional
|
|
41
|
+
`multipleOf` uses a 1e-9 quotient tolerance. Defaults remain
|
|
41
42
|
annotations and are not inserted into request input.
|
|
42
43
|
|
|
44
|
+
View and Procedure input objects (not Schemas or outputs) also take three rules
|
|
45
|
+
between their declared properties, each failing with one `INPUT_VALIDATION_FAILED`
|
|
46
|
+
error whose message names the rule:
|
|
47
|
+
|
|
48
|
+
- `dependentRequired: { weight: [unit] }`: "'unit' is required when 'weight' is
|
|
49
|
+
present.", at the missing field.
|
|
50
|
+
- `anyOf` whose every branch is exactly `{ required: [...] }`: "At least one of
|
|
51
|
+
'weight', 'heightCm' is required.", at the object.
|
|
52
|
+
- `if: { properties: { undo: { const: true } }, required: [undo] }` with `then`
|
|
53
|
+
and/or `else` each exactly `{ required: [...] }`: "'item' is required unless
|
|
54
|
+
'undo' is true.", at the missing field. `if` must list its field in `required`:
|
|
55
|
+
otherwise an absent field would satisfy it.
|
|
56
|
+
|
|
57
|
+
Any other `anyOf`/`if`/`then`/`else`, `oneOf`, `uniqueItems`, object property-count
|
|
58
|
+
bounds and other keywords and formats are refused at generation. The rules only
|
|
59
|
+
validate: types, SQL binds and Admin forms keep the declared fields, and Admin
|
|
60
|
+
shows the server's error at its field. `describe` keeps them as written; MCP and
|
|
61
|
+
WebMCP tools advertise the schema without them and append each rule's sentence to
|
|
62
|
+
the tool description, since some model APIs refuse these keywords in a tool's
|
|
63
|
+
input schema. [Trigger](trigger.md#input-rules) has an example.
|
|
64
|
+
|
|
43
65
|
Native `paged/views/<name>.ts` has a default export, callable as
|
|
44
66
|
`page(db, caller, input, { limit?, cursor? })` after importing it as `page`,
|
|
45
67
|
answering `{ rows, nextCursor? }`.
|
|
@@ -56,10 +78,18 @@ their contracts. Native integer bigint cursor keys are serialized separately.
|
|
|
56
78
|
REST/MCP use separate `paged/wire/views/<name>.ts` SQL when declared integers
|
|
57
79
|
require exact transport projections. Web pages and internal paged Views keep
|
|
58
80
|
driver-native rows. Internal paging creates no public route/tool.
|
|
59
|
-
Authored SQL `LIMIT`
|
|
81
|
+
Authored SQL `LIMIT` bounds the complete ordered row set, including across cursor pages.
|
|
82
|
+
Use `limit` in the paging options to choose each page size (up to 500); it does not expand the View's declared result set.
|
|
83
|
+
An MCP View page keeps the `limit` of 1 to 500 but holds only as many of those rows
|
|
84
|
+
as fit 60,000 characters of JSON, so a result stays under host truncation limits. It
|
|
85
|
+
is cut before the first row that overflows and its next cursor resumes at that row.
|
|
86
|
+
A single row over the budget is returned alone with the cursor, and a View without an
|
|
87
|
+
order stays one whole page; a page still over the budget carries a notice that hosts
|
|
88
|
+
may truncate it.
|
|
60
89
|
A pageable Schema View has a complete NOT NULL scalar tuple ending in the
|
|
61
90
|
compiler's `id` tiebreak. Its ordered columns must match an index after fixed
|
|
62
|
-
equality prefixes
|
|
91
|
+
equality prefixes; a user-field `IN` scope or restrict is not one (see
|
|
92
|
+
[index prefixes](schema.md#index-prefixes-for-paged-views)); multiple columns and mixed directions
|
|
63
93
|
retain the authored order. CTE projections preserve the proof; joins must have a
|
|
64
94
|
proved singleton side, or equality on the joined Schema's `id` or single-field
|
|
65
95
|
unique key, so the base id cannot fan out. LEFT JOIN accepts nullable or missing
|
|
@@ -132,7 +162,10 @@ const tags = await site.head(request, env); // HTML string, empty for unknown/mi
|
|
|
132
162
|
```
|
|
133
163
|
|
|
134
164
|
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
|
|
165
|
+
Page and list Views must return the page parameters; list Views must accept empty input: no
|
|
166
|
+
`required`, no root `anyOf`, `enum` or `const`, no `else` rule, and a root `type`, when given, that includes `object`.
|
|
167
|
+
These conditions also apply to each schema a root `$ref` chain reaches, and an unresolved or cyclic chain fails them.
|
|
168
|
+
`web()` throws a `TypeError` for any other list View. Markdown
|
|
136
169
|
must name a textual output column. HTML remains application-owned.
|
|
137
170
|
|
|
138
171
|
`/llms.txt` and `/sitemap.xml` traverse each list's `nextCursor`; `.md` and head
|
|
@@ -169,14 +202,30 @@ transport loads on the first matching request. Pass `handlers` when ref
|
|
|
169
202
|
Procedures are declared, as for REST. Admin tools are listed only on the admin
|
|
170
203
|
endpoint, and publishing visibility belongs to the generated SQL.
|
|
171
204
|
|
|
205
|
+
`apps.public` and `apps.admin` accept a `McpAppsSource` or an array mixing static
|
|
206
|
+
contributors with async `({ env, request }) => McpApps` factories. Static resources
|
|
207
|
+
are checked once at construction; factories resolve only after the endpoint's
|
|
208
|
+
origin and caller gates, and their resource identities and ownership are checked
|
|
209
|
+
on each request even when the tool names are unchanged.
|
|
210
|
+
|
|
211
|
+
Discovery is caller-independent by default (ADR-0014); listing a tool grants no
|
|
212
|
+
execution authority. Configure `authorize` to filter `tools/list` and the tool
|
|
213
|
+
names supplied to resource HTML loaders: that mode also excludes tools refused
|
|
214
|
+
by declared identity, scope and `check` requirements. Without `authorize`, the
|
|
215
|
+
complete endpoint catalogue is not a permission-filtered catalogue. Operation
|
|
216
|
+
validation, guards and credential checks still apply to calls.
|
|
217
|
+
|
|
172
218
|
The app resolves bearer tokens with `resolveCaller(request, auth, { audience:
|
|
173
219
|
new URL('/mcp', request.url).href })` (both surfaces use `<origin>/mcp`). The app
|
|
174
220
|
must also mount Better Auth's `/.well-known/oauth-protected-resource/mcp`
|
|
175
221
|
metadata route. Auth challenges point to that route. Presented bearer credentials
|
|
176
222
|
need the `mcp` scope; cookie sessions use the same-origin mutation checks.
|
|
177
223
|
|
|
178
|
-
Generation emits `mcp.ts` only for a non-internal View or MCP Trigger.
|
|
179
|
-
|
|
224
|
+
Generation emits `mcp.ts` only for a non-internal View or MCP Trigger. Omitting
|
|
225
|
+
MCP remains a deliberate asymmetric choice; an HTTP-only Procedure still generates
|
|
226
|
+
successfully with the warning described in [Trigger](trigger.md). These
|
|
227
|
+
surfaces typecheck without the optional `@modelcontextprotocol/server` peer;
|
|
228
|
+
install the exact version declared in the installed SDK's `peerDependencies`.
|
|
180
229
|
The peer loads by dynamic import on the first matching `/mcp` request; if
|
|
181
230
|
missing, that request fails at runtime. Publishing alone does not select MCP;
|
|
182
231
|
on a selected
|
|
@@ -207,15 +256,42 @@ as an explicit option.
|
|
|
207
256
|
### MCP catalogue and callbacks
|
|
208
257
|
|
|
209
258
|
`mcp({ db, caller, handlers?, locale?, instructions?, serverInfo?, requiredScopes?,
|
|
210
|
-
resourceMetadata?, onObservation?, onRefusal? })` keeps the SDK behind its first
|
|
259
|
+
resourceMetadata?, allowedOrigins?, onObservation?, onRefusal? })` keeps the SDK behind its first
|
|
211
260
|
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.
|
|
261
|
+
English, then their first entry. Tools carry the [tool hints](procedure.md#mcp-tool-hints). `instructions` is returned by initialize.
|
|
214
262
|
The scope floor defaults to `["mcp"]` for every credential except cookie sessions;
|
|
215
263
|
`requiredScopes: []` disables that floor. An anonymous closed surface challenges
|
|
216
264
|
from initialize, and every call in a batch is checked before any call runs.
|
|
217
265
|
`resourceMetadata` overrides the challenge URL for reverse proxies.
|
|
218
266
|
|
|
267
|
+
Both endpoints answer a request without `Origin`, which is what server-side MCP
|
|
268
|
+
clients send; a bearer credential needs none. A cookie session is ambient authority, so
|
|
269
|
+
`checkOrigin` also guards it: on a request that is not GET, HEAD or OPTIONS, at least one of
|
|
270
|
+
`Origin` and `Sec-Fetch-Site` must be present and every present one must agree (`Origin` equal
|
|
271
|
+
to the request's own origin; `Sec-Fetch-Site` `same-origin` or `none`), else `AUTH_DENIED` at
|
|
272
|
+
`request:origin`. On MCP it runs only for a tool call that executes (not a read-only call from an
|
|
273
|
+
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
|
|
274
|
+
(`localhost`, `127.0.0.1`, `[::1]`, any port) or an `allowedOrigins` entry, else
|
|
275
|
+
403 before authentication on every credential; `Origin: null` is refused. The
|
|
276
|
+
site's own origin is not implied, because a DNS-rebinding page sends an `Origin`
|
|
277
|
+
equal to the `Host` it rebound, so a browser client served from your deployed
|
|
278
|
+
site lists that origin: `allowedOrigins: ["https://app.example"]`. Entries are
|
|
279
|
+
compared by hostname as the official SDK does (not scheme or port); each is a
|
|
280
|
+
full origin (`https://console.example`) or the SDK's bare host
|
|
281
|
+
(`console.example`, `localhost:8787`), and anything else, such as a path, throws
|
|
282
|
+
when `mcp(...)` is configured; so does a wildcard such as `*` or
|
|
283
|
+
`https://*.example.com`, since entries are matched by exact hostname. `Host` is
|
|
284
|
+
not checked. Cookie writes also need the full same-origin check.
|
|
285
|
+
|
|
286
|
+
`/mcp` sends no CORS headers. `allowedOrigins` therefore serves pages on the
|
|
287
|
+
same origin as the endpoint, or clients behind a proxy that forwards their
|
|
288
|
+
`Origin`; a browser client on another origin also needs the app to answer
|
|
289
|
+
preflight and add CORS headers itself. Under `wrangler dev` with `routes` or
|
|
290
|
+
`dev.host`, Miniflare rewrites an `Origin` equal to the request's own origin to
|
|
291
|
+
the route or `dev.host` origin, and a DNS-rebinding request always has that
|
|
292
|
+
shape. Listing that host would admit rebinding against the local server, so keep
|
|
293
|
+
the route and `dev.host` hosts out of `allowedOrigins` in local development.
|
|
294
|
+
|
|
219
295
|
`onObservation` receives request refusals and invocation attempt/completion
|
|
220
296
|
metadata, including duration and outcome. Its Caller and scopes are frozen copies.
|
|
221
297
|
`onRefusal` receives credential/origin refusals. Neither callback receives request
|
|
@@ -232,6 +308,44 @@ with an output schema they omit `structuredContent` to avoid validating an error
|
|
|
232
308
|
against a success schema. Without one they also expose diagnostic structured
|
|
233
309
|
content. MCP adds no prompts or non-UI resources.
|
|
234
310
|
|
|
311
|
+
### Tool versions
|
|
312
|
+
|
|
313
|
+
Each generated tool (MCP, media, publishing and WebMCP) carries `version`, a
|
|
314
|
+
short hash computed by `mantle generate` of the definition as listed: name,
|
|
315
|
+
title, description without the marker, input schema (a View tool's with its
|
|
316
|
+
`limit` and `cursor` paging properties), output schema and annotations. A
|
|
317
|
+
localized title or description is hashed with all its locales. The description
|
|
318
|
+
ends with `(tool version <version>)`, so an unchanged tool keeps a
|
|
319
|
+
byte-identical description across deploys.
|
|
320
|
+
|
|
321
|
+
MCP results of a call that reaches the tool add a second text block,
|
|
322
|
+
`tool version <version>`; failures other than authorization refusals append
|
|
323
|
+
`; input schema <JSON>`, the input schema the tool is listed with.
|
|
324
|
+
`structuredContent` is unchanged. A `tools/call` naming a tool the endpoint
|
|
325
|
+
serves but the caller may not use is answered by the authentication or
|
|
326
|
+
authorization challenge (HTTP 401 or 403) before the SDK's transport and
|
|
327
|
+
request checks, even though that caller's `tools/list` omits the tool. A
|
|
328
|
+
`tools/call` naming a tool not in the endpoint's catalogue, in a batch or not
|
|
329
|
+
and from a host caller too, is answered with JSON-RPC error `-32602` and the
|
|
330
|
+
message
|
|
331
|
+
`No tool named "<name>" exists in the current catalogue (catalogue version <serverInfo.version>).`
|
|
332
|
+
For such a name the SDK's transport and request checks (headers, protocol
|
|
333
|
+
version, JSON-RPC shape) answer first, so a malformed request gets the same
|
|
334
|
+
refusal as one naming a served tool the caller may use.
|
|
335
|
+
Unless `serverInfo.version` is given, the kernel's `mcp()` sets it to
|
|
336
|
+
a hash of the `surface:name:version` of every tool the endpoint serves, before
|
|
337
|
+
per-caller filtering, so `/mcp` and `/mcp/admin` each report their own catalogue
|
|
338
|
+
version, and it changes when a generated tool's definition changes or a tool is
|
|
339
|
+
added or removed, however the tools are wired.
|
|
340
|
+
|
|
341
|
+
`POST /admin/api/webmcp/{tool}` returns `{ output, version }`. A refusal's
|
|
342
|
+
diagnostic adds `toolVersion`, and `inputSchema` unless it is an authorization
|
|
343
|
+
refusal; internal errors and refusals before the call carry neither. Admin's
|
|
344
|
+
WebMCP results repeat these as the same text block MCP adds. The navigation
|
|
345
|
+
tools `admin_get_context` and `admin_navigate` have no version. Tools an
|
|
346
|
+
application adds itself carry a version only if it sets one. Why: [MCP and
|
|
347
|
+
agents](../concepts/mcp-and-agents.md#when-a-host-shows-old-tools).
|
|
348
|
+
|
|
235
349
|
### Declared View scalars on the wire
|
|
236
350
|
|
|
237
351
|
REST and MCP View pages expose declared JSON columns as JSON values and declared
|
|
@@ -24,12 +24,54 @@ metadata: { name: close-ticket-http }
|
|
|
24
24
|
spec:
|
|
25
25
|
source: { kind: http, method: POST, path: '/api/tickets/{id}/close' }
|
|
26
26
|
target: { procedure: close-ticket }
|
|
27
|
+
---
|
|
28
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
29
|
+
kind: Trigger
|
|
30
|
+
metadata: { name: close-ticket-mcp }
|
|
31
|
+
spec:
|
|
32
|
+
source: { kind: mcp, surface: admin }
|
|
33
|
+
target: { procedure: close-ticket }
|
|
27
34
|
```
|
|
28
35
|
|
|
29
36
|
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
37
|
|
|
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
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
By default, pair every HTTP Trigger with an MCP Trigger so the same operation is reachable by the user's AI. `mantle generate` warns when a Procedure has HTTP exposure but no MCP exposure, suggesting `surface: admin` for an admin-only Procedure and `public` otherwise. Triggers still decide every Procedure surface: intentional asymmetric exposure is allowed and generation succeeds.
|
|
32
41
|
|
|
33
42
|
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
43
|
|
|
44
|
+
## Input rules
|
|
45
|
+
|
|
46
|
+
The admin MCP tool below lists `reason` and `closed` without the rules, and its
|
|
47
|
+
description ends "'reason' is required when 'closed' is false."; a call with
|
|
48
|
+
`closed: false` and no `reason` fails with `INPUT_VALIDATION_FAILED` at
|
|
49
|
+
`procedures/set-ticket/input/reason` (the error path is
|
|
50
|
+
`procedures/<name>/input/<field>`). See [surface contracts](surface.md) for the
|
|
51
|
+
supported rules.
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
55
|
+
kind: Procedure
|
|
56
|
+
metadata: { name: set-ticket }
|
|
57
|
+
spec:
|
|
58
|
+
description: Open or close a ticket.
|
|
59
|
+
input:
|
|
60
|
+
type: object
|
|
61
|
+
required: [id, closed]
|
|
62
|
+
properties: { id: { type: string }, closed: { type: boolean }, reason: { type: string, maxLength: 200 } }
|
|
63
|
+
additionalProperties: false
|
|
64
|
+
if: { properties: { closed: { const: false } }, required: [closed] }
|
|
65
|
+
then: { required: [reason] }
|
|
66
|
+
output: { type: object }
|
|
67
|
+
handler: { sql: 'UPDATE tickets SET closed=input.closed WHERE id=input.id RETURNING id,closed' }
|
|
68
|
+
---
|
|
69
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
70
|
+
kind: Trigger
|
|
71
|
+
metadata: { name: set-ticket-mcp }
|
|
72
|
+
spec:
|
|
73
|
+
source: { kind: mcp, surface: admin }
|
|
74
|
+
target: { procedure: set-ticket }
|
|
75
|
+
```
|
|
76
|
+
|
|
35
77
|
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.9
|
|
21
|
+
|
|
22
|
+
Pin every selected `@aotter/mantle`, `@aotter/mantle-ui` and
|
|
23
|
+
`@aotter/mantle-admin` dependency to `0.2.0-alpha.9`, 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.9/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
|
|