@aotter/mantle 0.1.5-alpha.1 → 0.2.0-alpha.10
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 +166 -298
- package/dist/auth/adapter.d.ts +32 -0
- package/dist/auth/adapter.js +288 -0
- package/dist/auth/admin.d.ts +3 -0
- package/dist/auth/admin.js +21 -0
- package/dist/auth/caller.d.ts +86 -0
- package/dist/auth/caller.js +155 -0
- package/dist/auth/hardening.d.ts +24 -0
- package/dist/auth/hardening.js +40 -0
- package/dist/auth/identity.d.ts +121 -0
- package/dist/auth/identity.js +203 -0
- package/dist/auth/oauth.d.ts +63 -0
- package/dist/auth/oauth.js +217 -0
- package/dist/auth/resource-seeds.js +10 -0
- package/dist/auth/routes.d.ts +32 -0
- package/dist/auth/routes.js +226 -0
- package/dist/build/compiler.d.ts +721 -0
- package/dist/build/compiler.js +7029 -0
- package/dist/build/spec/domain/model/ManifestGrammar.d.ts +439 -0
- package/dist/build/spec/domain/model/RuntimePlan.d.ts +168 -0
- package/dist/build/spec/domain/model/SqlIr.d.ts +129 -0
- package/dist/build/spec/domain/model/UserFields.d.ts +21 -0
- package/dist/build/spec/domain/service/PlanFingerprint.d.ts +7 -0
- package/dist/build/spec/kernel/diagnostic.d.ts +108 -0
- package/dist/bun/assets.d.ts +2 -0
- package/dist/bun/assets.js +24 -0
- package/dist/domain/adminOrder.js +24 -0
- package/dist/domain/columns.js +57 -0
- package/dist/domain/describe/chunk-ZVXBGP7Y.js +424 -0
- package/dist/domain/describe/index.d.ts +58 -0
- package/dist/domain/describe/index.js +115 -0
- package/dist/domain/describe/target-hash.d.ts +8 -0
- package/dist/domain/describe/target-hash.js +12 -0
- package/dist/domain/describe/targets.d.ts +40 -0
- package/dist/domain/entry.js +33 -0
- package/dist/domain/generated-tools.js +19 -0
- package/dist/domain/generation.d.ts +16 -0
- package/dist/domain/generation.js +1 -0
- package/dist/domain/input.js +17 -0
- package/dist/domain/model.js +1 -0
- package/dist/domain/paging.js +230 -0
- package/dist/domain/recipes.js +24 -0
- package/dist/domain/userReferences.js +5 -0
- package/dist/domain/wireScalars.js +47 -0
- package/dist/infrastructure/adminEntries.js +153 -0
- package/dist/infrastructure/adminPaging.js +72 -0
- package/dist/infrastructure/artifacts.js +71 -0
- package/dist/infrastructure/authSchema.js +83 -0
- package/dist/infrastructure/bundle.js +188 -0
- package/dist/infrastructure/cli.js +118 -0
- package/dist/infrastructure/generate.d.ts +12 -0
- package/dist/infrastructure/generate.js +189 -0
- package/dist/infrastructure/generateAuth.d.ts +8 -0
- package/dist/infrastructure/generateAuth.js +74 -0
- package/dist/infrastructure/help.js +60 -0
- package/dist/infrastructure/init.js +54 -0
- package/dist/infrastructure/inputs.js +41 -0
- package/dist/infrastructure/mounts.js +25 -0
- package/dist/infrastructure/wrangler.js +109 -0
- package/dist/kernel/authorization.d.ts +4 -0
- package/dist/kernel/authorization.js +16 -0
- package/dist/kernel/binds.d.ts +60 -0
- package/dist/kernel/binds.js +49 -0
- package/dist/kernel/caller-identity.d.ts +24 -0
- package/dist/kernel/caller-identity.js +22 -0
- package/dist/kernel/contract-error.d.ts +12 -0
- package/dist/kernel/contract-error.js +14 -0
- package/dist/kernel/contracts.d.ts +143 -0
- package/dist/kernel/contracts.js +97 -0
- package/dist/kernel/cron.d.ts +1 -0
- package/dist/kernel/cron.js +58 -0
- package/dist/kernel/dialect/common.d.ts +38 -0
- package/dist/kernel/dialect/common.js +6 -0
- package/dist/kernel/dialect/postgres.d.ts +16 -0
- package/dist/kernel/dialect/postgres.js +18 -0
- package/dist/kernel/dialect/sqlite.d.ts +20 -0
- package/dist/kernel/dialect/sqlite.js +34 -0
- package/dist/kernel/drain.d.ts +5 -0
- package/dist/kernel/drain.js +35 -0
- package/dist/kernel/driver/bun-sqlite.d.ts +19 -0
- package/dist/kernel/driver/bun-sqlite.js +77 -0
- package/dist/kernel/driver/d1.d.ts +24 -0
- package/dist/kernel/driver/d1.js +22 -0
- package/dist/kernel/driver/postgres.d.ts +25 -0
- package/dist/kernel/driver/postgres.js +99 -0
- package/dist/kernel/driver/transaction-context.d.ts +10 -0
- package/dist/kernel/driver/transaction-context.js +29 -0
- package/dist/kernel/email.d.ts +28 -0
- package/dist/kernel/email.js +38 -0
- package/dist/kernel/host-access.d.ts +10 -0
- package/dist/kernel/host-access.js +65 -0
- package/dist/kernel/host-caller.d.ts +39 -0
- package/dist/kernel/host-caller.js +56 -0
- package/dist/kernel/json-body.d.ts +4 -0
- package/dist/kernel/json-body.js +44 -0
- package/dist/kernel/mcp-apps.d.ts +69 -0
- package/dist/kernel/mcp-apps.js +40 -0
- package/dist/kernel/mcp-call.d.ts +5 -0
- package/dist/kernel/mcp-call.js +25 -0
- package/dist/kernel/mcp-handler.js +371 -0
- package/dist/kernel/mcp.d.ts +110 -0
- package/dist/kernel/mcp.js +50 -0
- package/dist/kernel/media.d.ts +27 -0
- package/dist/kernel/media.js +216 -0
- package/dist/kernel/paging.d.ts +55 -0
- package/dist/kernel/paging.js +95 -0
- package/dist/kernel/publishing.d.ts +4 -0
- package/dist/kernel/publishing.js +16 -0
- package/dist/kernel/r2-media.d.ts +42 -0
- package/dist/kernel/r2-media.js +78 -0
- package/dist/kernel/rest-dispatch.js +38 -0
- package/dist/kernel/rest-router.js +64 -0
- package/dist/kernel/rest.d.ts +58 -0
- package/dist/kernel/rest.js +41 -0
- package/dist/kernel/scalars.d.ts +6 -0
- package/dist/kernel/scalars.js +44 -0
- package/dist/kernel/search.js +5 -0
- package/dist/kernel/site-types.d.ts +146 -0
- package/dist/kernel/site-types.js +1 -0
- package/dist/kernel/site.d.ts +39 -0
- package/dist/kernel/site.js +32 -0
- package/dist/kernel/store.d.ts +175 -0
- package/dist/kernel/store.js +269 -0
- package/dist/kernel/surface.d.ts +10 -0
- package/dist/kernel/surface.js +19 -0
- package/dist/kernel/sweep.d.ts +17 -0
- package/dist/kernel/sweep.js +27 -0
- package/dist/kernel/translations.d.ts +1 -0
- package/dist/kernel/translations.js +13 -0
- package/dist/kernel/user-caller.d.ts +31 -0
- package/dist/kernel/user-caller.js +67 -0
- package/dist/kernel/user-field-name.js +3 -0
- package/dist/kernel/user-fields.d.ts +35 -0
- package/dist/kernel/user-fields.js +81 -0
- package/dist/kernel/validation.d.ts +15 -0
- package/dist/kernel/validation.js +54 -0
- package/dist/kernel/web-gateway.js +68 -0
- package/dist/kernel/web-head.js +15 -0
- package/dist/kernel/web-index.js +40 -0
- package/dist/kernel/web-response.js +8 -0
- package/dist/kernel/web.d.ts +77 -0
- package/dist/kernel/web.js +124 -0
- package/dist/kernel/where.js +27 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/usecase/adminBindings.js +143 -0
- package/dist/usecase/adminClient.js +87 -0
- package/dist/usecase/adminDescriptors.js +107 -0
- package/dist/usecase/generate.js +166 -0
- package/dist/usecase/handlers.js +21 -0
- package/dist/usecase/mcp-media.js +17 -0
- package/dist/usecase/mcp.js +100 -0
- package/dist/usecase/paging.js +71 -0
- package/dist/usecase/publishing.js +63 -0
- package/dist/usecase/refusals.js +27 -0
- package/dist/usecase/rest.js +54 -0
- package/dist/usecase/search.js +7 -0
- package/dist/usecase/site.js +39 -0
- package/dist/usecase/stores.js +64 -0
- package/dist/usecase/tool-version.js +9 -0
- package/dist/usecase/translations.js +78 -0
- package/dist/usecase/ttl.js +20 -0
- package/dist/usecase/validation.js +241 -0
- package/dist/usecase/web.js +23 -0
- package/docs/examples/README.md +39 -18
- package/docs/examples/app-shell/.dev.vars.example +4 -0
- package/docs/examples/app-shell/README.md +121 -0
- package/docs/examples/app-shell/accept.mjs +91 -0
- package/docs/examples/app-shell/gitignore +7 -0
- package/docs/examples/app-shell/package.json +43 -0
- package/docs/examples/app-shell/pnpm-workspace.yaml +8 -0
- package/docs/examples/app-shell/smoke-kit.mjs +168 -0
- package/docs/examples/app-shell/src/auth.ts +58 -0
- package/docs/examples/app-shell/src/handlers.ts +5 -0
- package/docs/examples/app-shell/src/index.ts +4 -0
- package/docs/examples/app-shell/src/routes.ts +14 -0
- package/docs/examples/app-shell/src/schedule.ts +8 -0
- package/docs/examples/app-shell/src/service.ts +70 -0
- package/docs/examples/app-shell/src/sign-in.ts +41 -0
- package/docs/examples/app-shell/src/signup.ts +8 -0
- package/docs/examples/app-shell/tsconfig.json +26 -0
- package/docs/examples/app-shell/worker-configuration.d.ts +22 -0
- package/docs/examples/app-shell/wrangler.jsonc +62 -0
- package/docs/examples/club-events/README.md +65 -0
- package/docs/examples/club-events/manifests/club-events.yaml +247 -0
- package/docs/examples/club-events/smoke.mjs +127 -0
- package/docs/examples/club-events/src/handlers.ts +25 -0
- package/docs/examples/club-events/src/signup.ts +9 -0
- package/docs/examples/generated-web/README.md +78 -0
- package/docs/examples/generated-web/posts.yaml +38 -0
- package/docs/examples/generated-web/seed.sql +6 -0
- package/docs/examples/generated-web/src/worker.ts +43 -0
- package/docs/examples/generated-web/tsconfig.json +8 -0
- package/docs/examples/generated-web/wrangler.jsonc +8 -0
- package/docs/examples/purchasing/.dev.vars.example +6 -0
- package/docs/examples/purchasing/README.md +99 -0
- package/docs/examples/purchasing/manifests/purchasing.yaml +374 -0
- package/docs/examples/purchasing/seed.sql +4 -0
- package/docs/examples/purchasing/smoke.mjs +191 -0
- package/docs/examples/purchasing/src/admin.tsx +13 -0
- package/docs/examples/purchasing/src/handlers.ts +33 -0
- package/docs/examples/purchasing/src/notify.ts +29 -0
- package/docs/examples/purchasing/src/requests-page.ts +57 -0
- package/docs/examples/purchasing/src/routes.ts +53 -0
- package/docs/examples/purchasing/src/schedule.ts +31 -0
- package/docs/examples/purchasing/src/signup.ts +5 -0
- package/docs/examples/purchasing/src/supplier.ts +32 -0
- package/docs/examples/purchasing/wrangler.jsonc +69 -0
- package/docs/examples/recipes/README.md +60 -0
- package/docs/examples/recipes/manifests/recipes.yaml +75 -0
- package/docs/examples/recipes/smoke.mjs +102 -0
- package/docs/examples/recipes/src/media.ts +13 -0
- package/docs/examples/recipes/src/pages.ts +99 -0
- package/docs/examples/recipes/src/routes.ts +71 -0
- package/docs/examples/recipes/src/site-config.ts +12 -0
- package/docs/examples/recipes/wrangler.jsonc +74 -0
- package/docs/handbook/cloudflare/authentication.md +180 -322
- package/docs/handbook/cloudflare/chatgpt-sites.md +5 -23
- package/docs/handbook/cloudflare/deploy-and-operate.md +55 -77
- package/docs/handbook/cloudflare/media-r2.md +61 -205
- package/docs/handbook/cloudflare/service-entry.md +239 -0
- package/docs/handbook/concepts/authorization.md +10 -107
- package/docs/handbook/concepts/four-atoms.md +9 -168
- package/docs/handbook/concepts/lifecycle-and-locales.md +9 -132
- package/docs/handbook/concepts/mcp-and-agents.md +24 -121
- package/docs/handbook/concepts/procedures-and-triggers.md +5 -217
- package/docs/handbook/concepts/runtime-and-adapters.md +15 -169
- package/docs/handbook/concepts/views.md +10 -185
- package/docs/handbook/examples/hub.md +3 -8
- package/docs/handbook/guides/adapter-guide.md +11 -0
- package/docs/handbook/guides/admin-ui.md +115 -133
- package/docs/handbook/guides/agent-setup.md +64 -74
- package/docs/handbook/guides/agent-workflow-acceptance.md +232 -0
- package/docs/handbook/guides/email.md +86 -0
- package/docs/handbook/guides/local-testing.md +40 -0
- package/docs/handbook/guides/mcp-apps.md +560 -0
- package/docs/handbook/guides/native-admin.md +566 -0
- package/docs/handbook/guides/native-execution.md +133 -0
- package/docs/handbook/guides/oauth.md +219 -0
- package/docs/handbook/guides/postgres-adapters.md +56 -0
- package/docs/handbook/guides/public-pages.md +87 -0
- package/docs/handbook/guides/size-and-startup.md +9 -0
- package/docs/handbook/guides/sql-patterns.md +266 -0
- package/docs/handbook/guides/typed-queries.md +211 -107
- package/docs/handbook/navigation.json +176 -55
- package/docs/handbook/reference/admin-descriptors.md +53 -0
- package/docs/handbook/reference/authorization.md +39 -266
- package/docs/handbook/reference/describe.md +56 -0
- package/docs/handbook/reference/diagnostics.md +19 -186
- package/docs/handbook/reference/features.md +45 -55
- package/docs/handbook/reference/manifest.md +6 -129
- package/docs/handbook/reference/procedure.md +11 -303
- package/docs/handbook/reference/schema.md +64 -240
- package/docs/handbook/reference/site-config.md +5 -218
- package/docs/handbook/reference/sql.md +64 -0
- package/docs/handbook/reference/surface.md +392 -201
- package/docs/handbook/reference/trigger.md +56 -192
- package/docs/handbook/reference/view.md +24 -243
- package/docs/handbook/releases/index.md +244 -77
- package/docs/handbook/start/agents.md +45 -0
- package/docs/handbook/start/overview.md +9 -48
- package/docs/handbook/start/project-and-cli.md +51 -104
- package/docs/handbook/start/quickstart-worker.md +43 -178
- package/docs/skills/develop/SKILL.md +79 -249
- package/docs/skills/integrate/SKILL.md +39 -50
- package/docs/skills/media-gc/SKILL.md +34 -10
- package/docs/skills/provision/SKILL.md +36 -186
- package/docs/skills/update/SKILL.md +23 -38
- package/docs/source/scripts/postgres-adapter.ts +101 -0
- package/docs/upgrade-0.1-to-0.2.md +227 -0
- package/package.json +120 -131
- package/skills/README.md +48 -108
- package/skills/mantle/SKILL.md +76 -0
- package/dist/admin-ui.d.ts +0 -2
- package/dist/admin-ui.d.ts.map +0 -1
- package/dist/admin-ui.js +0 -2
- package/dist/admin-ui.js.map +0 -1
- package/dist/admin.d.ts +0 -2
- package/dist/admin.d.ts.map +0 -1
- package/dist/admin.js +0 -2
- package/dist/admin.js.map +0 -1
- package/dist/auth.d.ts +0 -2
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -2
- package/dist/auth.js.map +0 -1
- package/dist/bun.d.ts +0 -2
- package/dist/bun.d.ts.map +0 -1
- package/dist/bun.js +0 -2
- package/dist/bun.js.map +0 -1
- package/dist/cli/generate-cloudflare.d.ts +0 -7
- package/dist/cli/generate-cloudflare.d.ts.map +0 -1
- package/dist/cli/generate-cloudflare.js +0 -123
- package/dist/cli/generate-cloudflare.js.map +0 -1
- package/dist/cli/generate-project.d.ts +0 -25
- package/dist/cli/generate-project.d.ts.map +0 -1
- package/dist/cli/generate-project.js +0 -282
- package/dist/cli/generate-project.js.map +0 -1
- package/dist/cli/generate-sites.d.ts +0 -8
- package/dist/cli/generate-sites.d.ts.map +0 -1
- package/dist/cli/generate-sites.js +0 -273
- package/dist/cli/generate-sites.js.map +0 -1
- package/dist/cli/generate.d.ts +0 -20
- package/dist/cli/generate.d.ts.map +0 -1
- package/dist/cli/generate.js +0 -299
- package/dist/cli/generate.js.map +0 -1
- package/dist/cli/harness.d.ts +0 -3
- package/dist/cli/harness.d.ts.map +0 -1
- package/dist/cli/harness.js +0 -214
- package/dist/cli/harness.js.map +0 -1
- package/dist/cli/main.d.ts +0 -3
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -77
- package/dist/cli/main.js.map +0 -1
- package/dist/cli/skills.d.ts +0 -3
- package/dist/cli/skills.d.ts.map +0 -1
- package/dist/cli/skills.js +0 -128
- package/dist/cli/skills.js.map +0 -1
- package/dist/cloudflare.d.ts +0 -2
- package/dist/cloudflare.d.ts.map +0 -1
- package/dist/cloudflare.js +0 -2
- package/dist/cloudflare.js.map +0 -1
- package/dist/codegen/emitMantleModule.d.ts +0 -22
- package/dist/codegen/emitMantleModule.d.ts.map +0 -1
- package/dist/codegen/emitMantleModule.js +0 -291
- package/dist/codegen/emitMantleModule.js.map +0 -1
- package/dist/codegen.d.ts +0 -2
- package/dist/codegen.d.ts.map +0 -1
- package/dist/codegen.js +0 -2
- package/dist/codegen.js.map +0 -1
- package/dist/runtime-testing.d.ts +0 -2
- package/dist/runtime-testing.d.ts.map +0 -1
- package/dist/runtime-testing.js +0 -2
- package/dist/runtime-testing.js.map +0 -1
- package/dist/runtime.d.ts +0 -2
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -2
- package/dist/runtime.js.map +0 -1
- package/dist/spec.d.ts +0 -2
- package/dist/spec.d.ts.map +0 -1
- package/dist/spec.js +0 -2
- package/dist/spec.js.map +0 -1
- package/dist/vercel-libsql.d.ts +0 -2
- package/dist/vercel-libsql.d.ts.map +0 -1
- package/dist/vercel-libsql.js +0 -2
- package/dist/vercel-libsql.js.map +0 -1
- package/dist/vercel.d.ts +0 -2
- package/dist/vercel.d.ts.map +0 -1
- package/dist/vercel.js +0 -2
- package/dist/vercel.js.map +0 -1
- package/dist/web.d.ts +0 -2
- package/dist/web.d.ts.map +0 -1
- package/dist/web.js +0 -2
- package/dist/web.js.map +0 -1
- package/docs/adapter-guide.md +0 -222
- package/docs/adr/0001-four-atom-manifest-model.md +0 -440
- package/docs/adr/0002-closed-enums-for-bindings.md +0 -253
- package/docs/adr/0007-ai-as-primary-author.md +0 -473
- package/docs/adr/0008-structured-diagnostic-shape.md +0 -338
- package/docs/adr/0009-consumer-supplied-manifests.md +0 -149
- package/docs/adr/0010-locale-and-translates.md +0 -453
- package/docs/adr/0011-adapter-port-spec.md +0 -281
- package/docs/adr/0012-views-as-public-rest.md +0 -161
- package/docs/adr/0013-agent-provisioned-consumer-projects.md +0 -154
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +0 -900
- package/docs/adr/0016-site-semantic-layer.md +0 -70
- package/docs/adr/0017-media-multi-variant-agent-side-optimization.md +0 -181
- package/docs/adr/0018-core-starters-repository-boundary.md +0 -179
- package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +0 -240
- package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +0 -199
- package/docs/adr/0021-retire-starter-scaffolding.md +0 -30
- package/docs/adr/0022-caller-observed-version-occ.md +0 -212
- package/docs/adr/0023-port-failure-contract.md +0 -69
- package/docs/adr/0024-manifest-native-schema-tables.md +0 -264
- package/docs/adr/0025-reserved-native-columns-and-published-only-public-views.md +0 -105
- package/docs/adr/0026-progressive-generate.md +0 -36
- package/docs/adr/0027-scheduled-procedure-triggers.md +0 -31
- package/docs/adr/0028-schema-ttl-expiry.md +0 -33
- package/docs/adr/README.md +0 -81
- package/docs/adr/adr-lite-803-request-diagnostics.md +0 -38
- package/docs/adr/adr-lite-808-route-readiness.md +0 -47
- package/docs/adr/adr-lite-809-bounded-public-content.md +0 -71
- package/docs/adr/adr-lite-812-native-parity.md +0 -150
- package/docs/adr/adr-lite-823-home-statistics.md +0 -64
- package/docs/adr/adr-lite-842-mcp-authoring.md +0 -30
- package/docs/adr/adr-lite-845-frontend-client.md +0 -38
- package/docs/adr/adr-lite-861-admin-webmcp.md +0 -42
- package/docs/adr/adr-lite-909-admin-ui-kit.md +0 -37
- package/docs/agent-prompts.md +0 -64
- package/docs/api-mcp-authorization.md +0 -5
- package/docs/assets/mantle-admin-developer.png +0 -0
- package/docs/assets/mantle-admin-operations.png +0 -0
- package/docs/assets/mantle-hero.jpg +0 -0
- package/docs/auth-hosting-model.md +0 -166
- package/docs/cloudflare-low-level-composition.md +0 -5
- package/docs/consumer-onboarding-audit.md +0 -65
- package/docs/deferred-lifecycle-queues.md +0 -5
- package/docs/design-atoms.md +0 -16
- package/docs/design-references/editorial-blog-2026-05-05.md +0 -218
- package/docs/direct-authoring.md +0 -5
- package/docs/examples/builtin-commerce.md +0 -269
- package/docs/examples/builtin-intake.md +0 -143
- package/docs/examples/builtin-legal-documents.md +0 -189
- package/docs/examples/builtin-procurement.md +0 -241
- package/docs/examples/builtin-publication.md +0 -241
- package/docs/examples/builtin-reservation.md +0 -149
- package/docs/examples/cf-primitives-commerce-inventory.md +0 -809
- package/docs/examples/cf-primitives-guarded-api.md +0 -429
- package/docs/examples/cf-primitives-intake-hooks.md +0 -319
- package/docs/examples/host-chatgpt-sites/.openai/hosting.json +0 -1
- package/docs/examples/host-chatgpt-sites/README.md +0 -70
- package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +0 -8
- package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +0 -294
- package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +0 -5
- package/docs/examples/host-chatgpt-sites/drizzle/0003_store_instance_id.sql +0 -1
- package/docs/examples/host-chatgpt-sites/drizzle/0004_mantle.sql +0 -10
- package/docs/examples/host-chatgpt-sites/drizzle/0005_mantle.sql +0 -24
- package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +0 -1
- package/docs/examples/host-chatgpt-sites/drizzle/meta/mantle-state.json +0 -60
- package/docs/examples/host-chatgpt-sites/manifests/site.yaml +0 -27
- package/docs/examples/host-chatgpt-sites/package.json +0 -1
- package/docs/examples/host-chatgpt-sites/public/site.css +0 -1
- package/docs/examples/host-chatgpt-sites/scripts/build.mjs +0 -13
- package/docs/examples/host-chatgpt-sites/scripts/check.mjs +0 -101
- package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +0 -43
- package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +0 -66
- package/docs/examples/host-chatgpt-sites/src/index.ts +0 -46
- package/docs/examples/host-chatgpt-sites/src/mcp.ts +0 -49
- package/docs/examples/host-chatgpt-sites/src/media.ts +0 -109
- package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +0 -38
- package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +0 -1
- package/docs/examples/host-chatgpt-sites/src/web.ts +0 -35
- package/docs/examples/host-chatgpt-sites/tsconfig.json +0 -1
- package/docs/examples/host-chatgpt-sites/wrangler.jsonc +0 -10
- package/docs/examples/host-local-admin-otp/.dev.vars.example +0 -3
- package/docs/examples/host-local-admin-otp/README.md +0 -80
- package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +0 -5
- package/docs/examples/host-local-admin-otp/manifests/site.yaml +0 -25
- package/docs/examples/host-local-admin-otp/package.json +0 -29
- package/docs/examples/host-local-admin-otp/public/.gitkeep +0 -1
- package/docs/examples/host-local-admin-otp/smoke.mjs +0 -141
- package/docs/examples/host-local-admin-otp/src/index.ts +0 -54
- package/docs/examples/host-local-admin-otp/tsconfig.json +0 -17
- package/docs/examples/host-local-admin-otp/wrangler.jsonc +0 -23
- package/docs/examples/host-minimal-worker/README.md +0 -41
- package/docs/examples/host-minimal-worker/manifests/site.yaml +0 -25
- package/docs/examples/host-minimal-worker/package.json +0 -26
- package/docs/examples/host-minimal-worker/smoke.mjs +0 -23
- package/docs/examples/host-minimal-worker/src/index.ts +0 -4
- package/docs/examples/host-minimal-worker/tsconfig.json +0 -17
- package/docs/examples/host-minimal-worker/wrangler.jsonc +0 -22
- package/docs/handbook/chatgpt-sites/equipment-checkout.md +0 -231
- package/docs/handbook/chatgpt-sites/generated-app.md +0 -84
- package/docs/handbook/chatgpt-sites/host-reference.md +0 -113
- package/docs/handbook/chatgpt-sites/index.md +0 -133
- package/docs/handbook/cloudflare/bindings.md +0 -291
- package/docs/handbook/cloudflare/conventional-worker.md +0 -166
- package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -170
- package/docs/handbook/cloudflare/low-level-composition.md +0 -169
- package/docs/handbook/cloudflare/public-web.md +0 -144
- package/docs/handbook/cloudflare/site-chrome.md +0 -75
- package/docs/handbook/examples/commerce-transaction.md +0 -11
- package/docs/handbook/examples/commerce.md +0 -11
- package/docs/handbook/examples/guarded-api.md +0 -10
- package/docs/handbook/examples/intake-form.md +0 -12
- package/docs/handbook/examples/intake-hooks.md +0 -11
- package/docs/handbook/examples/legal-documents.md +0 -10
- package/docs/handbook/examples/procurement-approvals.md +0 -10
- package/docs/handbook/examples/publication.md +0 -10
- package/docs/handbook/examples/reservation.md +0 -10
- package/docs/handbook/start/quickstart-admin.md +0 -52
- package/docs/labels.md +0 -71
- package/docs/media-uploads.md +0 -5
- package/docs/migration-0.1.2.md +0 -13
- package/docs/performance-harness.md +0 -228
- package/docs/release-process.md +0 -244
- package/docs/schema-indexes.md +0 -5
- package/docs/sealed-pipeline-ownership.md +0 -101
- package/docs/skills/plugin/SKILL.md +0 -132
- package/docs/skills/theme/SKILL.md +0 -77
- package/docs/spec-only-host-adoption.md +0 -157
- package/docs/transaction-patterns.md +0 -5
- package/skills/develop/SKILL.md +0 -258
- package/skills/install/SKILL.md +0 -112
- package/skills/integrate/SKILL.md +0 -60
- package/skills/media-gc/SKILL.md +0 -85
- package/skills/plugin/SKILL.md +0 -132
- package/skills/provision/SKILL.md +0 -194
- package/skills/theme/SKILL.md +0 -77
- package/skills/update/SKILL.md +0 -51
- package/templates/chatgpt-sites/auth.ts +0 -66
- package/templates/chatgpt-sites/build.mjs +0 -14
- package/templates/chatgpt-sites/env.ts +0 -1
- package/templates/chatgpt-sites/mcp.ts +0 -48
- package/templates/chatgpt-sites/public-mcp.ts +0 -29
- package/templates/chatgpt-sites/smoke-local.mjs +0 -20
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Seven SQL patterns most apps need, each a complete manifest that compiles on every target, with the PostgreSQL spelling and what each refuses.
|
|
3
|
+
---
|
|
4
|
+
# SQL patterns
|
|
5
|
+
|
|
6
|
+
Each section is a business need, the manifest that meets it, and what the database refuses. Every block here is
|
|
7
|
+
generated by the docs check, and `packages/mantle/tests/sql-patterns.test.ts` runs the same manifest on bun:sqlite,
|
|
8
|
+
local D1 and PostgreSQL through the generated REST boundary. The SQL is the SQLite and D1 spelling; where PostgreSQL
|
|
9
|
+
differs, the section says so. What each dialect accepts is listed in [the SQL reference](../reference/sql.md).
|
|
10
|
+
|
|
11
|
+
For an order with any number of lines, see [several rows that commit together](typed-queries.md#several-rows-that-commit-together).
|
|
12
|
+
|
|
13
|
+
The sections share two Schemas:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
17
|
+
kind: Schema
|
|
18
|
+
metadata: { name: products }
|
|
19
|
+
spec:
|
|
20
|
+
title: Products
|
|
21
|
+
lifecycle: operational
|
|
22
|
+
uniqueIndexes: [[sku]]
|
|
23
|
+
checks: ["reserved <= stock"]
|
|
24
|
+
schema:
|
|
25
|
+
type: object
|
|
26
|
+
required: [sku, name, price, stock, reserved, active]
|
|
27
|
+
properties:
|
|
28
|
+
sku: { type: string }
|
|
29
|
+
name: { type: string, minLength: 1 }
|
|
30
|
+
note: { type: string }
|
|
31
|
+
price: { type: integer, minimum: 0 }
|
|
32
|
+
stock: { type: integer, minimum: 0 }
|
|
33
|
+
reserved: { type: integer, minimum: 0 }
|
|
34
|
+
active: { type: boolean }
|
|
35
|
+
---
|
|
36
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
37
|
+
kind: Schema
|
|
38
|
+
metadata: { name: orders }
|
|
39
|
+
spec:
|
|
40
|
+
title: Orders
|
|
41
|
+
lifecycle: operational
|
|
42
|
+
indexes: [[productId, createdAt, id]]
|
|
43
|
+
schema:
|
|
44
|
+
type: object
|
|
45
|
+
required: [productId, qty, total, orderStatus]
|
|
46
|
+
properties:
|
|
47
|
+
productId: { type: string, x-mantle-ref: products }
|
|
48
|
+
qty: { type: integer, minimum: 1 }
|
|
49
|
+
total: { type: integer, minimum: 0 }
|
|
50
|
+
orderStatus: { type: string, enum: [reserved, pending_approval] }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Choose a status while writing: CASE
|
|
54
|
+
|
|
55
|
+
An order over 5000 waits for a manager; a smaller one reserves stock at once. `CASE` picks the status inside the
|
|
56
|
+
INSERT, and the reservation runs only for a small order, in the same program, so the two never disagree. The total
|
|
57
|
+
comes from the catalogue's `price`, never from the caller: a price in the input would let a caller choose the total,
|
|
58
|
+
and with it whether the order needs approval.
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
62
|
+
kind: Procedure
|
|
63
|
+
metadata: { name: place-order }
|
|
64
|
+
spec:
|
|
65
|
+
description: Order a product. An order over 5000 waits for approval and reserves nothing yet.
|
|
66
|
+
requires: { auth: { all: [ctx.user] } }
|
|
67
|
+
input:
|
|
68
|
+
type: object
|
|
69
|
+
additionalProperties: false
|
|
70
|
+
required: [productId, qty]
|
|
71
|
+
properties:
|
|
72
|
+
productId: { type: string }
|
|
73
|
+
qty: { type: integer, minimum: 1 }
|
|
74
|
+
output: { type: object }
|
|
75
|
+
handler:
|
|
76
|
+
sql: |
|
|
77
|
+
INSERT INTO orders (productId, qty, total, orderStatus)
|
|
78
|
+
SELECT p.id, input.qty, input.qty * p.price,
|
|
79
|
+
CASE WHEN input.qty * p.price > 5000 THEN 'pending_approval' ELSE 'reserved' END
|
|
80
|
+
FROM products p WHERE p.id = input.productId AND p.active
|
|
81
|
+
RETURNING id, orderStatus AS "orderStatus";
|
|
82
|
+
UPDATE products SET reserved = reserved + input.qty
|
|
83
|
+
WHERE id IN (SELECT p.id FROM products p WHERE p.id = input.productId AND p.active AND input.qty * p.price <= 5000)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The second statement is written as `WHERE id IN (SELECT …)` on purpose. `WHERE id = input.productId AND …` is a
|
|
87
|
+
row-classified UPDATE, which must change exactly one row: for an order that waits, it would change none and refuse the
|
|
88
|
+
whole call with `CONFLICT`. A write that should do nothing when its condition is false needs the set form. The first
|
|
89
|
+
statement's join on `products` with `p.active` means an unknown or discontinued product writes no order.
|
|
90
|
+
|
|
91
|
+
## Approve before reserving
|
|
92
|
+
|
|
93
|
+
Approval changes the status and reserves the stock in one program. The first statement is the guard: a stale
|
|
94
|
+
`expectedVersion` or an order that is no longer `pending_approval` matches no row, which is `CONFLICT`. The second
|
|
95
|
+
statement fails `reserved <= stock` when the stock is gone by now, and that rolls back the status change too, so the
|
|
96
|
+
order stays pending. [Purchasing](../../examples/purchasing/README.md) does the same with a department budget
|
|
97
|
+
(`approve-request-write`), and approves small requests at submission with `CASE` (`submit-request`).
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
101
|
+
kind: Procedure
|
|
102
|
+
metadata: { name: approve-order }
|
|
103
|
+
spec:
|
|
104
|
+
description: Approve a pending order at the version you reviewed; reserves its stock.
|
|
105
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
106
|
+
input:
|
|
107
|
+
type: object
|
|
108
|
+
additionalProperties: false
|
|
109
|
+
required: [id, expectedVersion]
|
|
110
|
+
properties:
|
|
111
|
+
id: { type: string }
|
|
112
|
+
expectedVersion: { type: integer }
|
|
113
|
+
output: { type: object }
|
|
114
|
+
handler:
|
|
115
|
+
sql: |
|
|
116
|
+
UPDATE orders SET orderStatus = 'reserved'
|
|
117
|
+
WHERE id = input.id AND version = input.expectedVersion AND orderStatus = 'pending_approval'
|
|
118
|
+
RETURNING id, version;
|
|
119
|
+
UPDATE products SET reserved = reserved + (SELECT o.qty FROM orders o WHERE o.id = input.id)
|
|
120
|
+
WHERE id = (SELECT o.productId FROM orders o WHERE o.id = input.id)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Change only the fields given: COALESCE
|
|
124
|
+
|
|
125
|
+
A partial update keeps every field the caller left out: `COALESCE(input.name, name)` is the new name when one is given
|
|
126
|
+
and the current one otherwise. `NULLIF(input.note, '')` also treats an empty string as left out. Because of that, this
|
|
127
|
+
Procedure cannot clear a note; give clearing its own input (for example `clearNote: true` with
|
|
128
|
+
`CASE WHEN input.clearNote THEN NULL ELSE … END`) if the app needs it. The version guard makes a stale edit a 409.
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
132
|
+
kind: Procedure
|
|
133
|
+
metadata: { name: update-product }
|
|
134
|
+
spec:
|
|
135
|
+
description: Change a product's name or note; a field you leave out keeps its value.
|
|
136
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
137
|
+
input:
|
|
138
|
+
type: object
|
|
139
|
+
additionalProperties: false
|
|
140
|
+
required: [id, expectedVersion]
|
|
141
|
+
properties:
|
|
142
|
+
id: { type: string }
|
|
143
|
+
expectedVersion: { type: integer }
|
|
144
|
+
name: { type: string, minLength: 1 }
|
|
145
|
+
note: { type: string }
|
|
146
|
+
output: { type: object }
|
|
147
|
+
handler:
|
|
148
|
+
sql: |
|
|
149
|
+
UPDATE products SET name = COALESCE(input.name, name), note = COALESCE(NULLIF(input.note, ''), note)
|
|
150
|
+
WHERE id = input.id AND version = input.expectedVersion
|
|
151
|
+
RETURNING id, version, name, note
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Create or add: ON CONFLICT DO UPDATE
|
|
155
|
+
|
|
156
|
+
Restocking a SKU creates the product the first time and adds to its stock afterwards, in one statement. The conflict
|
|
157
|
+
target must be a declared unique index (`uniqueIndexes: [[sku]]`). `excluded` is the row the INSERT proposed;
|
|
158
|
+
qualify the existing row with the table name (`products.stock`).
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
162
|
+
kind: Procedure
|
|
163
|
+
metadata: { name: restock }
|
|
164
|
+
spec:
|
|
165
|
+
description: Add stock by SKU, creating the product the first time.
|
|
166
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
167
|
+
input:
|
|
168
|
+
type: object
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
required: [sku, name, price, qty]
|
|
171
|
+
properties:
|
|
172
|
+
sku: { type: string }
|
|
173
|
+
name: { type: string, minLength: 1 }
|
|
174
|
+
price: { type: integer, minimum: 0 }
|
|
175
|
+
qty: { type: integer, minimum: 1 }
|
|
176
|
+
output: { type: object }
|
|
177
|
+
handler:
|
|
178
|
+
sql: |
|
|
179
|
+
INSERT INTO products (sku, name, price, stock, reserved, active) VALUES (input.sku, input.name, input.price, input.qty, 0, true)
|
|
180
|
+
ON CONFLICT (sku) DO UPDATE SET stock = products.stock + excluded.stock
|
|
181
|
+
RETURNING id, stock
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Refuse while something refers to it: NOT EXISTS
|
|
185
|
+
|
|
186
|
+
A product cannot be discontinued while an order for it waits for approval, since approving that order would reserve
|
|
187
|
+
stock of a product no longer sold. `NOT EXISTS (…)` is part of the guard, so while such an order exists the
|
|
188
|
+
row-classified UPDATE changes nothing and the call is `CONFLICT`; the product stays on sale. Use `EXISTS` the same way
|
|
189
|
+
for "only if a related row exists". [Purchasing](../../examples/purchasing/README.md) uses `NOT EXISTS` to write the
|
|
190
|
+
ledger row only for the request its program just approved (`submit-request`).
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
194
|
+
kind: Procedure
|
|
195
|
+
metadata: { name: discontinue-product }
|
|
196
|
+
spec:
|
|
197
|
+
description: Stop selling a product; refused while an order for it waits for approval.
|
|
198
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
199
|
+
input:
|
|
200
|
+
type: object
|
|
201
|
+
additionalProperties: false
|
|
202
|
+
required: [id]
|
|
203
|
+
properties:
|
|
204
|
+
id: { type: string }
|
|
205
|
+
output: { type: object }
|
|
206
|
+
handler:
|
|
207
|
+
sql: |
|
|
208
|
+
UPDATE products SET active = false
|
|
209
|
+
WHERE id = input.id
|
|
210
|
+
AND NOT EXISTS (SELECT 1 FROM orders o WHERE o.productId = products.id AND o.orderStatus = 'pending_approval')
|
|
211
|
+
RETURNING id
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## A row with its children in one read
|
|
215
|
+
|
|
216
|
+
An admin, or an AI client, reads each product with its orders in one call instead of one call per product. A
|
|
217
|
+
correlated subquery builds the array, so a product without orders has `[]`; `LEFT JOIN … GROUP BY` would give it one
|
|
218
|
+
empty object instead.
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
222
|
+
kind: View
|
|
223
|
+
metadata: { name: products-with-orders }
|
|
224
|
+
spec:
|
|
225
|
+
description: Each product with its orders as one JSON array.
|
|
226
|
+
surface: admin
|
|
227
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
228
|
+
sql: |
|
|
229
|
+
SELECT p.id, p.name,
|
|
230
|
+
(SELECT json_group_array(json_set('{}', '$.id', o.id, '$.qty', o.qty)) FROM orders o WHERE o.productId = p.id) AS orders
|
|
231
|
+
FROM products p ORDER BY p.id
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
SQLite and D1 build each object with `json_set('{}', '$.id', o.id, …)` (`json_object` is not on the allowlist).
|
|
235
|
+
PostgreSQL writes it as `COALESCE(jsonb_agg(jsonb_build_object('id', o.id, 'qty', o.qty)), CAST('[]' AS jsonb))`;
|
|
236
|
+
`jsonb_agg` of no rows is `NULL`, hence the `COALESCE`. PostgreSQL can also order inside the aggregate
|
|
237
|
+
(`jsonb_agg(… ORDER BY o.id)`); SQLite cannot, so sort the array.
|
|
238
|
+
|
|
239
|
+
Generation types the column as JSON because its expression can only produce a JSON document: a `json_group_array`,
|
|
240
|
+
`json_group_object`, `json_set`, `json_insert`, `json_remove`, `jsonb_agg`, `jsonb_object_agg`, `jsonb_build_object`,
|
|
241
|
+
`jsonb_build_array`, `jsonb_strip_nulls`, `to_jsonb` or `CAST(… AS jsonb)` call, a scalar subquery of one, or a
|
|
242
|
+
`COALESCE` of those with JSON literal fallbacks such as `'[]'`. REST, MCP (`structuredContent`) and Admin therefore
|
|
243
|
+
return an array on every target, and `WireRow` types the column `unknown`. The bare View and the Store keep the
|
|
244
|
+
driver's value: on SQLite and D1 that is JSON text, so code calling the bare View parses it. `json_extract`, `->>`
|
|
245
|
+
and `CASE` can return a scalar, so their output stays native.
|
|
246
|
+
|
|
247
|
+
## The latest row of each group: row_number()
|
|
248
|
+
|
|
249
|
+
The most recent order of each product. `row_number() OVER (PARTITION BY … ORDER BY …)` numbers each product's orders
|
|
250
|
+
newest first, and the outer query keeps number 1. End the window's ORDER BY with `id` so two rows created in the same
|
|
251
|
+
instant still have one order. `rank()` instead gives tied rows the same number.
|
|
252
|
+
|
|
253
|
+
```yaml
|
|
254
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
255
|
+
kind: View
|
|
256
|
+
metadata: { name: latest-order-per-product }
|
|
257
|
+
spec:
|
|
258
|
+
description: The most recent order of each product.
|
|
259
|
+
surface: admin
|
|
260
|
+
requires: { auth: { all: [ctx.admin] } }
|
|
261
|
+
sql: |
|
|
262
|
+
SELECT x.productId AS "productId", x.id, x.qty FROM (
|
|
263
|
+
SELECT o.productId, o.id, o.qty, row_number() OVER (PARTITION BY o.productId ORDER BY o.created_at DESC, o.id DESC) AS n
|
|
264
|
+
FROM orders o
|
|
265
|
+
) x WHERE x.n = 1 ORDER BY x.productId
|
|
266
|
+
```
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Choose Store for writes and simple lookups; declare internal Views for richer reads.
|
|
3
3
|
---
|
|
4
4
|
# Query from TypeScript
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
| Work | Data path |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| Writes | Store `insert` / `update` / `delete`, atomic `batch`, supported `transaction`, or declared Procedures |
|
|
9
|
+
| Id lookup or simple AND filter | Store `findById` / `find` / `first`, inside handlers or an existing application |
|
|
10
|
+
| Joins, aggregates, OR, reusable reads | Internal View: manifest SQL that is reviewable and typed |
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Never write raw SQL to Mantle collections: it bypasses scope, TTL, publishing and system-column rules. The Store applies row rules but does not check a View's or Procedure's `requires`; authorize your handler before using it.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
## Declare an internal View
|
|
15
|
+
|
|
16
|
+
Save this as `manifests/tickets.yaml`:
|
|
15
17
|
|
|
16
18
|
```yaml
|
|
17
|
-
apiVersion: cms.mantle.aotter.net/
|
|
19
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
18
20
|
kind: Schema
|
|
19
|
-
metadata:
|
|
20
|
-
name: tickets
|
|
21
|
+
metadata: { name: tickets }
|
|
21
22
|
spec:
|
|
22
23
|
title: Tickets
|
|
23
24
|
lifecycle: operational
|
|
25
|
+
indexes: [[ticketState]]
|
|
24
26
|
schema:
|
|
25
27
|
type: object
|
|
26
28
|
additionalProperties: false
|
|
@@ -28,134 +30,236 @@ spec:
|
|
|
28
30
|
properties:
|
|
29
31
|
subject: { type: string }
|
|
30
32
|
ticketState: { type: string, enum: [open, closed] }
|
|
31
|
-
indexes: [[ticketState]]
|
|
32
33
|
---
|
|
33
|
-
apiVersion: cms.mantle.aotter.net/
|
|
34
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
34
35
|
kind: View
|
|
35
|
-
metadata:
|
|
36
|
-
name: tickets-by-state
|
|
36
|
+
metadata: { name: tickets-by-state }
|
|
37
37
|
spec:
|
|
38
38
|
surface: internal
|
|
39
|
-
|
|
40
|
-
fields: [id, subject, ticketState]
|
|
41
|
-
filter:
|
|
42
|
-
eq: { field: ticketState, value: { $param: ticketState } }
|
|
43
|
-
params:
|
|
39
|
+
input:
|
|
44
40
|
type: object
|
|
45
41
|
additionalProperties: false
|
|
46
42
|
required: [ticketState]
|
|
47
43
|
properties:
|
|
48
44
|
ticketState: { type: string, enum: [open, closed] }
|
|
49
|
-
|
|
45
|
+
sql: |
|
|
46
|
+
SELECT id, subject, ticketState AS "ticketState" FROM tickets
|
|
47
|
+
WHERE ticketState = input.ticketState
|
|
48
|
+
ORDER BY id ASC
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
Generate with `mantle generate cloudflare-d1 generated manifests/tickets.yaml`.
|
|
52
|
+
An internal View has no public REST route or MCP tool. Its generated function still applies declared authorization and compiled row policy.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { store } from './generated/generated.js';
|
|
56
|
+
import ticketsByState from './generated/views/tickets-by-state.js';
|
|
57
|
+
import type { Caller } from '@aotter/mantle/contracts';
|
|
58
|
+
|
|
59
|
+
export async function readTickets(db: Parameters<typeof store>[0], caller: Caller) {
|
|
60
|
+
const tickets = store(db, caller).collection('tickets');
|
|
61
|
+
const first = await tickets.first({ where: { ticketstate: 'open' } });
|
|
62
|
+
const rows = await ticketsByState(db, caller, { ticketState: 'open' });
|
|
63
|
+
return { first, rows };
|
|
64
|
+
}
|
|
56
65
|
```
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
Admin reports. It remains in the plan and generated binding. It is not a
|
|
60
|
-
security bypass: adding `requires` evaluates the same authorization and guards
|
|
61
|
-
against the host-supplied `ctx` on every call. No `uiSchema` or shared HTTP
|
|
62
|
-
cache is allowed for an internal View.
|
|
67
|
+
The app supplies a verified Caller and its existing native handle. External input needs boundary validation. Store rows, filters and writes use storage names (`ticketstate`, `created_at`); View aliases use the SQL output names.
|
|
63
68
|
|
|
64
|
-
##
|
|
69
|
+
## Store reads and writes
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
typed API. This function can live in `src/queries.ts`:
|
|
71
|
+
`find` returns `{ rows, cursor }` (null cursor on the last page), with limits 1–500 and opaque keyset cursors. `where` combines fields with AND; `orderBy` selects a generated indexed column with `id` ties. Most order columns must be NOT NULL; publishing `published_at` supports nulls. For a proved pageable internal View, import `paged/views/<encoded-name>.ts` and call `(db, caller, input, { limit?, cursor? })`. Other internal Views retain their bare row-array function.
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
import type { MantleRuntime } from "@aotter/mantle/runtime";
|
|
71
|
-
import { bindMantle } from "../.mantle/generated/mantle.js";
|
|
72
|
-
|
|
73
|
-
export async function openTickets(runtime: MantleRuntime) {
|
|
74
|
-
const mantle = bindMantle(runtime);
|
|
75
|
-
const result = await mantle.views.ticketsByState({
|
|
76
|
-
params: { ticketState: "open" },
|
|
77
|
-
page: 1,
|
|
78
|
-
show: 20,
|
|
79
|
-
});
|
|
80
|
-
if (!result.ok) throw new Error(result.diagnostic.message);
|
|
81
|
-
return result.result.rows;
|
|
82
|
-
}
|
|
83
|
-
```
|
|
73
|
+
Store keeps `find({ search: 'text' })`, `first({ search: 'text' })` and `count(where?)`. Search matches exact id or declared searchable fields under the same row policy.
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
REST wraps those rows under `data` instead.
|
|
75
|
+
`count` scans every matching row. For large tables or frequently shown totals, use an internal View with indexed predicates or avoid exact totals; a View's aggregate still scans the matching rows.
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
make the request and `params` mandatory; invalid enum values are TypeScript
|
|
90
|
-
errors, and Runtime also validates actual inputs. `show` remains capped by
|
|
91
|
-
`limit`. For an authorized View, pass the verified caller context as `ctx`;
|
|
92
|
-
do not fabricate staff/user identities from request input.
|
|
77
|
+
Store search, Admin list search and Views using `mantle.search` share the same dialect search. SQLite/D1 use FTS5 trigram for queries of 3 or more characters, and LIKE for shorter queries. PostgreSQL uses ILIKE over the declared fields, which scans by default. A View does not make PostgreSQL search faster. For large PostgreSQL tables, the application's agent adds trigram indexes in a migration; ILIKE then uses them with no code change:
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
```sql
|
|
80
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
81
|
+
CREATE INDEX articles_title_trgm ON articles USING gin (title gin_trgm_ops);
|
|
82
|
+
```
|
|
98
83
|
|
|
99
|
-
|
|
84
|
+
Index each searched field as needed and inspect EXPLAIN for the workload. Case folding follows the engine.
|
|
100
85
|
|
|
101
|
-
|
|
102
|
-
|---|---|---|
|
|
103
|
-
| Declarative View | `Mantle.ViewParams_<name>` and `Mantle.ViewRow_<name>` | Projection follows `fields`; native columns have their native types. Data properties remain optional in the row type. |
|
|
104
|
-
| SQL View | Typed params; row type `unknown` | The generator does not infer SQL expressions or aliases. Narrow/validate rows in host code. |
|
|
105
|
-
| Entry field reader | `MantleEntry<Mantle.Entry_<schema>>` | The field must be a declared data property and the value a compatible string, number or boolean. Types do not prove an index exists. |
|
|
106
|
-
| Dynamic Runtime call | `runtime.executeView({ view, ctx, options })` | Useful without codegen; supplying a generic row type is the caller's assertion, not SQL validation. |
|
|
86
|
+
Writes are lazy and execute once when awaited. Insert/update return native rows; delete returns void. `update(id, set, { lock })` and `delete(id, { lock })` require the version the caller reviewed. A stale lock, excluded row or unique clash is `CONFLICT`. `batch` atomically executes unused Writes from one Store in input order; a step cannot use a prior result.
|
|
107
87
|
|
|
108
|
-
|
|
109
|
-
properties. Use explicit projections on exposed reads. Public declarative
|
|
110
|
-
Views over publishing Schemas inject `status = published`; internal/staff
|
|
111
|
-
Views and SQL statements do not. See [View reference](../reference/view.md).
|
|
88
|
+
Interactive `transaction(async tx => …)` exists on bun:sqlite and PostgreSQL, with collections, Views and Procedures bound to the transaction. D1 supports batches only. PostgreSQL defaults to serializable, with an explicit isolation override; SQLite uses BEGIN IMMEDIATE. Use `tx` inside the callback, not the outer Store or handle. Nested transactions and `tx.batch` are refused; there is no automatic retry.
|
|
112
89
|
|
|
113
|
-
|
|
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.
|
|
114
91
|
|
|
115
|
-
|
|
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.
|
|
116
93
|
|
|
117
|
-
|
|
118
|
-
const rows = await mantle.entries.tickets.findManyByDataField({
|
|
119
|
-
field: "ticketState",
|
|
120
|
-
value: "open",
|
|
121
|
-
limit: 20,
|
|
122
|
-
});
|
|
123
|
-
// rows[n].data is the generated tickets data shape.
|
|
124
|
-
```
|
|
94
|
+
## Prefer Store-backed ref Procedures for simple writes
|
|
125
95
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
132
102
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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.
|
|
139
112
|
|
|
140
|
-
|
|
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.
|
|
141
118
|
|
|
142
|
-
|
|
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.
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
|
|
129
|
+
## Several rows that commit together
|
|
130
|
+
|
|
131
|
+
An order with any number of lines, a cart checkout or a bulk import must commit
|
|
132
|
+
every row or none. Pass the rows as one array input and write them in one SQL
|
|
133
|
+
Procedure that reads the array as a row source: `json_each(input.items)` on
|
|
134
|
+
SQLite and D1, `jsonb_array_elements(input.items) AS j(value)` on PostgreSQL.
|
|
135
|
+
The Procedure's statements commit together on every target, so a Schema
|
|
136
|
+
`checks` failure in any statement refuses the whole call with `CONFLICT` and
|
|
137
|
+
leaves nothing behind. Bound the array with `maxItems`.
|
|
138
|
+
|
|
139
|
+
A statement cannot read an earlier statement's `RETURNING`, so rows that must
|
|
140
|
+
refer to each other share a key from the input: here the caller (or a `ref`
|
|
141
|
+
handler that then calls this Procedure) supplies `orderRef`. A later statement
|
|
142
|
+
does see the rows an earlier one wrote, so it can also store the parent's id
|
|
143
|
+
(`(SELECT o.id FROM orders o WHERE o.orderRef = input.orderRef)`) in a field with
|
|
144
|
+
`x-mantle-ref: orders`, which lets Admin fold the lines under their order.
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
```yaml
|
|
147
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
148
|
+
kind: Schema
|
|
149
|
+
metadata: { name: products }
|
|
150
|
+
spec:
|
|
151
|
+
title: Products
|
|
152
|
+
lifecycle: operational
|
|
153
|
+
checks: ["reserved <= stock"]
|
|
154
|
+
schema:
|
|
155
|
+
type: object
|
|
156
|
+
required: [name, stock, reserved]
|
|
157
|
+
properties:
|
|
158
|
+
name: { type: string }
|
|
159
|
+
stock: { type: integer, minimum: 0 }
|
|
160
|
+
reserved: { type: integer, minimum: 0 }
|
|
161
|
+
---
|
|
162
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
163
|
+
kind: Schema
|
|
164
|
+
metadata: { name: orders }
|
|
165
|
+
spec:
|
|
166
|
+
title: Orders
|
|
167
|
+
lifecycle: operational
|
|
168
|
+
uniqueIndexes: [[orderRef]]
|
|
169
|
+
checks: ["matchedLines = lineCount"]
|
|
170
|
+
schema:
|
|
171
|
+
type: object
|
|
172
|
+
required: [orderRef, lineCount, matchedLines]
|
|
173
|
+
properties:
|
|
174
|
+
orderRef: { type: string }
|
|
175
|
+
lineCount: { type: integer, minimum: 1 }
|
|
176
|
+
matchedLines: { type: integer, minimum: 0 }
|
|
177
|
+
---
|
|
178
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
179
|
+
kind: Schema
|
|
180
|
+
metadata: { name: order_lines }
|
|
181
|
+
spec:
|
|
182
|
+
title: Order lines
|
|
183
|
+
lifecycle: operational
|
|
184
|
+
schema:
|
|
185
|
+
type: object
|
|
186
|
+
required: [orderRef, productId, qty]
|
|
187
|
+
properties:
|
|
188
|
+
orderRef: { type: string }
|
|
189
|
+
productId: { type: string, x-mantle-ref: products }
|
|
190
|
+
qty: { type: integer, minimum: 1 }
|
|
191
|
+
---
|
|
192
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
193
|
+
kind: Procedure
|
|
194
|
+
metadata: { name: place-order }
|
|
195
|
+
spec:
|
|
196
|
+
description: Place an order of one or more lines; an unknown product or a line that would oversell refuses the whole order.
|
|
197
|
+
requires: { auth: { all: [ctx.user] } }
|
|
198
|
+
input:
|
|
199
|
+
type: object
|
|
200
|
+
additionalProperties: false
|
|
201
|
+
required: [orderRef, items]
|
|
202
|
+
properties:
|
|
203
|
+
orderRef: { type: string }
|
|
204
|
+
items:
|
|
205
|
+
type: array
|
|
206
|
+
minItems: 1
|
|
207
|
+
maxItems: 50
|
|
208
|
+
items:
|
|
209
|
+
type: object
|
|
210
|
+
additionalProperties: false
|
|
211
|
+
required: [productId, qty]
|
|
212
|
+
properties:
|
|
213
|
+
productId: { type: string }
|
|
214
|
+
qty: { type: integer, minimum: 1 }
|
|
215
|
+
output: { type: object }
|
|
216
|
+
handler:
|
|
217
|
+
sql: |
|
|
218
|
+
INSERT INTO orders (orderRef, lineCount, matchedLines) VALUES (input.orderRef,
|
|
219
|
+
(SELECT COUNT(*) FROM json_each(input.items) j),
|
|
220
|
+
(SELECT COUNT(*) FROM json_each(input.items) j JOIN products p ON p.id = j.value ->> 'productId'));
|
|
221
|
+
UPDATE products SET reserved = reserved + (SELECT SUM(j.value ->> 'qty') FROM json_each(input.items) j WHERE j.value ->> 'productId' = products.id)
|
|
222
|
+
WHERE id IN (SELECT j.value ->> 'productId' FROM json_each(input.items) j);
|
|
223
|
+
INSERT INTO order_lines (orderRef, productId, qty)
|
|
224
|
+
SELECT input.orderRef, p.id, j.value ->> 'qty' FROM json_each(input.items) j JOIN products p ON p.id = j.value ->> 'productId'
|
|
225
|
+
RETURNING id, productId AS "productId";
|
|
150
226
|
```
|
|
151
227
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
228
|
+
Each refusal is a check or a unique index, so it rolls back all three statements:
|
|
229
|
+
|
|
230
|
+
- A line that would oversell fails `reserved <= stock`. Repeated lines for one
|
|
231
|
+
product add up in the `SUM`, so they are checked against the same stock.
|
|
232
|
+
- A line naming an unknown product does not match in the join, so
|
|
233
|
+
`matchedLines` falls short of `lineCount` and the `orders` check fails. Set
|
|
234
|
+
operations alone would skip that line and still succeed.
|
|
235
|
+
- A repeated `orderRef` clashes with the unique index, so replaying a request
|
|
236
|
+
cannot reserve twice.
|
|
155
237
|
|
|
156
|
-
|
|
238
|
+
On PostgreSQL `->>` returns text: write `CAST(j.value ->> 'qty' AS int8)`
|
|
239
|
+
there. SQLite and D1 refuse that integer cast (`SQL_TYPE`, because SQLite
|
|
240
|
+
truncates where PostgreSQL rounds), and their `->>` already returns the JSON
|
|
241
|
+
number.
|
|
157
242
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
243
|
+
[SQL patterns](sql-patterns.md) covers approving before reserving, choosing a status with `CASE`, partial updates,
|
|
244
|
+
upserts, `EXISTS` guards and nested reads on the same `products`.
|
|
245
|
+
|
|
246
|
+
Do not split this into a Store write, then a SQL Procedure, then a compensating
|
|
247
|
+
delete: the rows are visible between the steps and a failure leaves cleanup to
|
|
248
|
+
the handler. A `ref` handler that needs the rows written together calls this
|
|
249
|
+
Procedure through `ctx.store.procedures`, passing the array itself: the
|
|
250
|
+
generated input type is the authored one, and the generated query serializes
|
|
251
|
+
the array for the driver (a string is bound as it is). Like a direct call, the
|
|
252
|
+
Store does not validate input against the Procedure's schema (REST and MCP do),
|
|
253
|
+
so `maxItems` and the line shape hold only for rows the handler has checked or
|
|
254
|
+
built.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { store } from './generated/generated.js';
|
|
258
|
+
import type { Caller } from '@aotter/mantle/contracts';
|
|
259
|
+
|
|
260
|
+
// `ctx.store` in a `ref` handler is this Store: `ctx.store.procedures['place-order']({ orderRef, items })`.
|
|
261
|
+
export async function placeOrder(db: Parameters<typeof store>[0], caller: Caller, orderRef: string) {
|
|
262
|
+
const items = [{ productId: 'pen', qty: 2 }, { productId: 'stapler', qty: 1 }];
|
|
263
|
+
return store(db, caller).procedures['place-order']({ orderRef, items });
|
|
264
|
+
}
|
|
265
|
+
```
|