@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,63 @@
|
|
|
1
|
+
/** Opt-in lifecycle: one atomic, version-guarded statement per transition, with Core's row rules. */
|
|
2
|
+
import { rowPolicy } from "../build/compiler.js";
|
|
3
|
+
import { dialect as sqlite } from "../kernel/dialect/sqlite.js";
|
|
4
|
+
import { dialect as postgres } from "../kernel/dialect/postgres.js";
|
|
5
|
+
import { renderTerm } from "../kernel/store.js";
|
|
6
|
+
import { publishedParent } from "./translations.js";
|
|
7
|
+
import { entry, exportName, q } from "../domain/entry.js";
|
|
8
|
+
import { publishingAnnotations, publishingInput, publishingTransitions } from "../domain/generated-tools.js";
|
|
9
|
+
export { publishingAnnotations, publishingInput, publishingTransitions };
|
|
10
|
+
export function emitPublishing(plan, isSqlite, declarations, procedures, entries, values, refuse) {
|
|
11
|
+
const d = isSqlite ? sqlite : postgres;
|
|
12
|
+
const translations = Object.values(plan.schemas).some(s => s.translates);
|
|
13
|
+
if (translations)
|
|
14
|
+
declarations.push('import {translationWrite} from "@aotter/mantle/driver/transaction-context";');
|
|
15
|
+
if (Object.values(plan.schemas).some(s => s.publishing))
|
|
16
|
+
declarations.push('import { transition } from "@aotter/mantle/publishing";');
|
|
17
|
+
for (const s of Object.values(plan.schemas).filter(s => s.publishing).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
18
|
+
if (Object.hasOwn(s.fields, "published_at")) {
|
|
19
|
+
refuse(`schemas/${s.name}`, "published_at is a publishing system column; remove the declared field.");
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (s.ttl)
|
|
23
|
+
values.add("cutoff");
|
|
24
|
+
for (const [verb, from, to] of publishingTransitions) {
|
|
25
|
+
const name = `${s.name}.${verb}`, fn = exportName("procedure", name), path = `procedures/${name}`;
|
|
26
|
+
const failure = `${verb}: row is missing or excluded, expectedVersion differs, status is not ${from.join(" or ")}${verb === "publish" ? `, or required data is missing${s.translates ? ", or no visible published parent shares the join key" : ""}` : ""}.`;
|
|
27
|
+
if (plan.procedures[name]) {
|
|
28
|
+
refuse(path, "This name is reserved for the generated publishing transition.");
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const statement = (trusted) => {
|
|
32
|
+
const schema = plan.schemas[s.name];
|
|
33
|
+
const recipes = [{ k: "now" }, { k: "input", name: "id" }, { k: "input", name: "expectedVersion" }];
|
|
34
|
+
const conditions = [`id = ${d.param(2)}`, `version = ${d.param(3)}`, `status IN (${from.map(v => `'${v}'`).join(", ")})`];
|
|
35
|
+
// Same visibility as Store and Admin writes, translation parent included (a child under an out-of-set parent is missing);
|
|
36
|
+
// the status term is replaced by the transition's own `status IN (...)`.
|
|
37
|
+
for (const term of rowPolicy(schema, trusted ? "trusted" : "caller", plan.schemas))
|
|
38
|
+
if (term[1] !== "admin")
|
|
39
|
+
conditions.push(renderTerm(d, term, term[1] === "parent" ? d.ident(s.name) + "." : "", recipe => (recipes.push(recipe), d.param(recipes.length))));
|
|
40
|
+
if (verb === "publish")
|
|
41
|
+
for (const field of s.schema.required ?? []) {
|
|
42
|
+
const column = Object.keys(s.fields).find(c => (s.names?.[c] ?? c) === field);
|
|
43
|
+
if (column)
|
|
44
|
+
conditions.push(`${d.ident(column)} IS NOT NULL`);
|
|
45
|
+
}
|
|
46
|
+
if (verb === "publish" && s.translates)
|
|
47
|
+
conditions.push(publishedParent(plan, schema, d, recipes, trusted));
|
|
48
|
+
const clock = d.param(1);
|
|
49
|
+
return { sql: `UPDATE ${d.ident(s.name)} SET status = '${to}', published_at = ${verb === "publish" ? clock : "NULL"}, version = version + 1, updated_at = ${clock} WHERE ${conditions.join(" AND ")} RETURNING id, version, status, ${d.wireTimestamp(d.ident("published_at")).join("")} AS published_at`, binds: recipes, row: true };
|
|
50
|
+
};
|
|
51
|
+
values.add("now");
|
|
52
|
+
const caller = q([statement(false)]), trusted = q([statement(true)]);
|
|
53
|
+
declarations.push(`export type ${fn}Input = { readonly id: string; readonly expectedVersion: number };`, `const ${fn}Caller = ${caller} as const;`, ...(trusted === caller ? [] : [`const ${fn}Trusted = ${trusted} as const;`]), `async function ${fn}(db: Database, caller: Caller, raw: ${fn}Input): Promise<Output> { ${plan.access ? 'USER.modeOf(caller); ' : ''}return transition(db, caller, raw, ${q(path)}, ${plan.access ? "USER.scopedAll(" : ""}${trusted === caller ? "" : `${plan.access ? 'USER.modeOf(caller) === "trusted"' : 'caller.kind === "system"'} ? ${fn}Trusted : `}${fn}Caller${plan.access ? ", caller)" : ""}, ${q(failure)}, D, DRIVER.procedure${translations ? `, () => translationWrite(db, ${q(s.name)}, ${verb === "publish" && s.translates ? q(s.translates.parent) : "undefined"})` : ""}); }`, `export { ${fn} };`);
|
|
54
|
+
procedures.push(`[${q(name)}]: ${fn}`);
|
|
55
|
+
entries[`procedures/${encodeURIComponent(name)}.ts`] = entry("procedure", name, `${fn}Input as Input`, 'export type { Output } from "@aotter/mantle/contracts";\n');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Publishing lists get one index, prefixed by scope when present. */
|
|
60
|
+
export function publishingSql(schemas, isSqlite) {
|
|
61
|
+
const d = isSqlite ? sqlite : postgres;
|
|
62
|
+
return Object.values(schemas).filter(s => s.publishing).map(s => `CREATE INDEX ${d.ident(`_mantle_ix_${s.name}_published`)} ON ${d.ident(s.name)} (${[...(s.scope ? [s.scope] : []), ...(s.userScopes ?? []).map(u => u.column), "published_at", "id"].map(d.ident).join(", ")})`);
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { viewOutputs } from "../build/compiler.js";
|
|
2
|
+
export function refuseDeclarations({ plan }, diagnostics) {
|
|
3
|
+
const refuse = (path, message) => diagnostics.push({ code: "GENERATE_UNSUPPORTED", path, message });
|
|
4
|
+
for (const [name, trigger] of Object.entries(plan.triggers)) {
|
|
5
|
+
const kind = trigger.source.kind;
|
|
6
|
+
if (kind === "http" || kind === "mcp")
|
|
7
|
+
continue;
|
|
8
|
+
refuse(`triggers/${name}`, kind === "schedule" ? "Schedule Triggers are unsupported. Wire scheduled() yourself." : "Before/after lifecycle hooks are unsupported. Write a custom handler.");
|
|
9
|
+
}
|
|
10
|
+
for (const [name, schema] of Object.entries(plan.schemas)) {
|
|
11
|
+
const parent = schema.translates && plan.schemas[schema.translates.parent.toLowerCase()];
|
|
12
|
+
if (parent?.scope && schema.scope !== parent.scope)
|
|
13
|
+
refuse(`schemas/${name}/translates`, `Scoped translation parent '${schema.translates.parent}' requires the same child scope column '${parent.scope}'. Different or missing child scope columns cannot be correlated safely.`);
|
|
14
|
+
}
|
|
15
|
+
for (const [kind, declarations] of [["views", plan.views], ["procedures", plan.procedures]]) {
|
|
16
|
+
for (const [name, declaration] of Object.entries(declarations)) {
|
|
17
|
+
if (declaration.requires?.guard)
|
|
18
|
+
refuse(`${kind}/${name}/requires/guard`, "Dynamic authorization guards are unsupported. Write a custom handler that checks authorization before executing queries.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const [name, view] of Object.entries(plan.views)) {
|
|
22
|
+
for (const [key, column] of Object.entries(viewOutputs(view, plan.schemas, new Map(), true).columns))
|
|
23
|
+
if (plan.schemas[column.schema]?.fields[column.field] === "geo") {
|
|
24
|
+
refuse(`views/${name}/columns/${key}`, "Native geo projection is unsupported. Select the latitude and longitude columns explicitly.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { databaseImport, q, viewImports } from "../domain/entry.js";
|
|
2
|
+
import { authLines, conversions } from "../domain/input.js";
|
|
3
|
+
import { boundaryPage, outputConversions } from "../domain/columns.js";
|
|
4
|
+
export function emitRest(plan, lowered, target, description) {
|
|
5
|
+
const guard = (requires, path, binds = [], trustedOnly = false) => {
|
|
6
|
+
const lines = authLines(requires, path, binds, !target.endsWith("postgres"), trustedOnly);
|
|
7
|
+
return lines.length ? `, check: (caller) => {${lines.join("")}}` : "";
|
|
8
|
+
};
|
|
9
|
+
const userImport = plan.access ? ['import { userBinds } from "@aotter/mantle/user-fields";'] : [];
|
|
10
|
+
const refs = Object.values(plan.procedures).some(p => "ref" in p.handler);
|
|
11
|
+
const imports = [...userImport, 'import { rest as serve, type Options } from "@aotter/mantle/rest";', databaseImport(target), ...(refs ? ['import { store, type Handlers } from "./generated.js";'] : [])];
|
|
12
|
+
if (Object.values(plan.procedures).some(p => p.trustedOnly) || [...Object.values(plan.views), ...Object.values(plan.procedures)].some(p => p.requires?.auth?.all.length))
|
|
13
|
+
imports.push('import { deny, isAdmin, isUser } from "@aotter/mantle/contracts";');
|
|
14
|
+
const views = [], procedures = [];
|
|
15
|
+
let hasWire = false;
|
|
16
|
+
const convert = (code) => code ? `, convert: ${code}` : "";
|
|
17
|
+
// Each segment matches in its encoded form; `{name}` segments capture.
|
|
18
|
+
const route = (method, path) => {
|
|
19
|
+
const keys = [];
|
|
20
|
+
const source = path.split("/").map(segment => segment.startsWith("{") && segment.endsWith("}") ? (keys.push(segment.slice(1, -1)), "([^/]+)") : encodeURIComponent(segment).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/");
|
|
21
|
+
return `method: ${q(method)}, pattern: new RegExp(${q("^" + source + "/?$")})${keys.length ? `, keys: ${q(keys)}` : ""}`;
|
|
22
|
+
};
|
|
23
|
+
for (const [name, view] of Object.entries(plan.views)) {
|
|
24
|
+
if (view.surface !== "public")
|
|
25
|
+
continue;
|
|
26
|
+
const id = views.length, encoded = encodeURIComponent(name);
|
|
27
|
+
const wire = outputConversions(plan, name, target);
|
|
28
|
+
imports.push(...viewImports(name, id).map(line => wire.some(([, type]) => type === "int") ? line.replaceAll("./paged/views/", "./paged/wire/views/") : line));
|
|
29
|
+
hasWire ||= wire.length > 0;
|
|
30
|
+
views.push(`{ ${route("GET", `/api/views/${name}`)}, target: ${q({ kind: "view", name })}${guard(view.requires, `views/${name}`, (lowered.views[name]?.public ?? lowered.views[name]?.caller)?.binds)}, query: query${id}, page: ${boundaryPage(`page${id}`, wire)}${convert(conversions(view.input?.properties, (lowered.views[name]?.public ?? lowered.views[name]?.caller)?.binds ?? [], target))} }`);
|
|
31
|
+
}
|
|
32
|
+
const triggers = Object.values(plan.triggers).filter(t => t.source.kind === "http").sort((a, b) => a.source.kind === "http" && b.source.kind === "http" ? (a.source.path.match(/\{/g)?.length ?? 0) - (b.source.path.match(/\{/g)?.length ?? 0) : 0);
|
|
33
|
+
for (const trigger of triggers) {
|
|
34
|
+
if (trigger.source.kind !== "http")
|
|
35
|
+
continue;
|
|
36
|
+
const { method, path } = trigger.source, name = trigger.procedure, procedure = plan.procedures[name], id = procedures.length, ref = "ref" in procedure.handler;
|
|
37
|
+
if (!ref)
|
|
38
|
+
imports.push(`import procedure${id} from "./procedures/${encodeURIComponent(name)}.js";`);
|
|
39
|
+
imports.push(`import { validate as validate${id} } from "./validation/procedures/${encodeURIComponent(name)}.js";`);
|
|
40
|
+
const params = Object.fromEntries([...path.matchAll(/\{([^}]+)\}/g)].flatMap(([, key]) => {
|
|
41
|
+
const type = [procedure.input?.properties?.[key]?.type].flat().find(t => t && t !== "null");
|
|
42
|
+
return type === "integer" || type === "number" ? [[key, "number"]] : type === "boolean" ? [[key, "boolean"]] : [];
|
|
43
|
+
}));
|
|
44
|
+
procedures.push(`{ ${route(method, path)}, id: ${q(`rest:${method} ${path.replace(/\{([^}]+)\}/g, ":$1")}`)}, target: ${q({ kind: "procedure", name })}${description.targets.find(t => t._meta.id === `procedure:${name}`)?._meta.readOnly ? ", readOnly: true" : ""}${guard(procedure.requires, `procedures/${name}`, lowered.procedures[name]?.caller?.flatMap(s => s.binds), procedure.trustedOnly)}${Object.keys(params).length ? `, params: ${q(params)}` : ""}, validate: validate${id}${convert(conversions(procedure.input?.properties, lowered.procedures[name]?.caller?.flatMap(s => s.binds) ?? [], target))}, run: ${ref ? `(db, caller, input, handlers) => store(db, caller, { handlers }).procedures[${q(name)}](input)` : `procedure${id}`} }`);
|
|
45
|
+
}
|
|
46
|
+
if (hasWire)
|
|
47
|
+
imports.push('import { wireRows } from "@aotter/mantle/contracts";');
|
|
48
|
+
const list = (items) => items.length ? `[\n${items.map(item => " " + item + ",\n").join("")} ]` : "[]";
|
|
49
|
+
return { "rest.ts": `${imports.join("\n")}
|
|
50
|
+
export function rest<Env>(options: Options<Env, Database>${refs ? " & { handlers: Handlers }" : ""}): (request: Request, env: Env, ctx?: import("@aotter/mantle/contracts").RequestContext) => Promise<Response | undefined> {
|
|
51
|
+
return serve<Env, Database${refs ? ", Handlers" : ""}>(${list([...views, ...procedures])}, ${plan.access ? `{...options, fields: ${q(plan.access.userFields)}, accessTargets: () => import("./access-targets.js").then(m => m.targets)}` : '{...options, accessTargets: () => import("./access-targets.js").then(m => m.targets)}'});
|
|
52
|
+
}
|
|
53
|
+
` };
|
|
54
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function searchDiagnostics(warnings) {
|
|
2
|
+
return [...new Set(warnings.filter(message => /mantle\.search|search fields/.test(message)).map(message => message.replace(/^(plan#\/[^ ]+) \([^)]*\): /, "$1: ")))].map(message => ({
|
|
3
|
+
code: "GENERATE_UNSUPPORTED",
|
|
4
|
+
path: /^plan#\/([^: ]+)/.exec(message)?.[1] ?? "views",
|
|
5
|
+
message: `${message}. Declare searchableFields on the source Schema; search_rank needs mantle.search on the same alias in the same query.`,
|
|
6
|
+
}));
|
|
7
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Generate the optional site/media module and deployment DDL, never runtime preparation. */
|
|
2
|
+
import { assertSiteDefaultsCanonical, canonicalizeLocaleList, expandPolicyRequired } from "../build/compiler.js";
|
|
3
|
+
import { driverFor, isSqlite, q } from "../domain/entry.js";
|
|
4
|
+
export function emitSite(target, defaults, refuse, schemaNames) {
|
|
5
|
+
if (!defaults)
|
|
6
|
+
return {};
|
|
7
|
+
try {
|
|
8
|
+
assertSiteDefaultsCanonical(defaults);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
refuse("site", String(error.message));
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
for (const name of schemaNames)
|
|
15
|
+
if (["site_config", "media_assets", "pending_media_uploads"].includes(name.toLowerCase()))
|
|
16
|
+
refuse(`schemas/${name}`, "This table name is reserved by the selected site/media module.");
|
|
17
|
+
if (new Set(defaults.media?.purposes?.map(p => p.name)).size !== (defaults.media?.purposes?.length ?? 0))
|
|
18
|
+
refuse("site/media/purposes", "Media purpose names must be unique.");
|
|
19
|
+
defaults = { ...defaults, ...(defaults.locales ? { locales: canonicalizeLocaleList(defaults.locales).valid } : {}) };
|
|
20
|
+
const pg = !isSqlite(target), driver = driverFor(target);
|
|
21
|
+
const policies = defaults.media?.purposes?.map(p => ({ ...p, required: expandPolicyRequired(p.required).map(slot => slot.join(",")) })) ?? [];
|
|
22
|
+
const seed = ["brand", "title", "description"].filter(key => defaults[key]).map(key => `INSERT INTO site_config (key,value) VALUES ('${key}','${defaults[key].replaceAll("'", "''")}') ON CONFLICT(key) DO NOTHING;`).join("\n");
|
|
23
|
+
const root = "@aotter/mantle";
|
|
24
|
+
return { "site.ts": `import { createSite } from "${root}/site";
|
|
25
|
+
import type { Caller } from "${root}/contracts";
|
|
26
|
+
import type { ${pg ? "Postgres" : "Handle"} as Database } from "${root}/${pg ? "contracts" : `driver/${driver}`}";
|
|
27
|
+
import * as DRIVER from "${root}/driver/${driver}";
|
|
28
|
+
import { param, escapeLike, likeEscape, like } from "${root}/dialect/${pg ? "postgres" : "sqlite"}";
|
|
29
|
+
export const siteDefaults = ${q(defaults)} as const;
|
|
30
|
+
const policies = ${q(policies)} as const;
|
|
31
|
+
export function site(db: Database, caller: Caller) { return createSite({read:(sql,values)=>DRIVER.read(db,sql,values),write:steps=>DRIVER.procedure(db,steps,"site"),definitive:DRIVER.definitive},{param,escapeLike,likeEscape,like},caller,siteDefaults,policies); }
|
|
32
|
+
`, "site-seed.sql": seed + "\n", "site-schema.sql": `CREATE TABLE site_config (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
33
|
+
CREATE TABLE media_assets (id TEXT PRIMARY KEY, created_at ${pg ? "BIGINT" : "INTEGER"} NOT NULL, owner_id TEXT, alt TEXT, caption TEXT, variants TEXT NOT NULL, metadata TEXT);
|
|
34
|
+
CREATE INDEX media_assets_by_created_id ON media_assets (created_at DESC, id DESC);
|
|
35
|
+
CREATE TABLE pending_media_uploads (id TEXT PRIMARY KEY, record TEXT NOT NULL, expires_at ${pg ? "BIGINT" : "INTEGER"} NOT NULL);
|
|
36
|
+
CREATE INDEX pending_media_uploads_expires_at ON pending_media_uploads (expires_at);
|
|
37
|
+
${seed}
|
|
38
|
+
` };
|
|
39
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One typed collection per Schema, over the generic `createStore`. Core's row rules (`rowPolicy`, `writePolicy`) are evaluated
|
|
3
|
+
* here, at build time, ONCE for a placeholder scope and TTL into the shared `Rules`; a Schema adds only a `Fact` (scope, TTL,
|
|
4
|
+
* publishing, its date-time and date columns). Generation is refused if a Schema's expanded rules differ from Core's own for it
|
|
5
|
+
* (caller and trusted rows, insert fills, update sets and lock, delete lock).
|
|
6
|
+
* Column names, value types, writable columns and `orderBy` are TypeScript types only: nothing at runtime knows them.
|
|
7
|
+
*/
|
|
8
|
+
import { NATIVE_NOT_NULL, notNullColumns, rowPolicy, writePolicy } from "../build/compiler.js";
|
|
9
|
+
import { declaredType, inputType, nativeType } from "../domain/columns.js";
|
|
10
|
+
import { q } from "../domain/entry.js";
|
|
11
|
+
import { expand, SCOPE, TTL } from "../kernel/store.js";
|
|
12
|
+
import { refChecks, userScopeTerm } from '../build/compiler.js';
|
|
13
|
+
// Types the typed Store compares and writes as plain bind values. Fixed numerics are scaled integers on SQLite; geo is two columns.
|
|
14
|
+
const SCALAR = new Set(["text", "real", "float8", "integer", "int4", "int8", "bool", "timestamptz", "date"]);
|
|
15
|
+
/** Core's own rules for a Schema, in the shape of `Rules` without the id expression (a delete's lock is compared separately). */
|
|
16
|
+
const core = (s, schemas) => {
|
|
17
|
+
const update = writePolicy(s, "update", { lock: true });
|
|
18
|
+
return { caller: rowPolicy(s, "caller", schemas), trusted: rowPolicy(s, "trusted", schemas), fills: writePolicy(s, "insert").fills, sets: update.sets, lock: update.lock };
|
|
19
|
+
};
|
|
20
|
+
const probe = { scope: SCOPE, ttl: TTL, ttlSeconds: 0, publishing: true };
|
|
21
|
+
/** @internal */
|
|
22
|
+
export function emitStores(schemas, isSqlite, newId, refuse, required = false) {
|
|
23
|
+
const all = Object.values(schemas).sort((a, b) => a.name < b.name ? -1 : 1);
|
|
24
|
+
if (!all.length && !required)
|
|
25
|
+
return undefined;
|
|
26
|
+
const rules = { ...core(probe), newId };
|
|
27
|
+
const tables = [], facts = [];
|
|
28
|
+
for (const s of all) {
|
|
29
|
+
const type = (column) => declaredType(s.fields, column) ?? (column === "version" ? "integer" : column === "published_at" ? "timestamptz" : "text");
|
|
30
|
+
const declared = Object.keys(s.fields).filter(f => f !== s.scope && s.fields[f] !== "geo");
|
|
31
|
+
const geo = Object.keys(s.fields).filter(f => s.fields[f] === "geo").flatMap(f => [`${f}_lat`, `${f}_lng`]); // `*` returns a geo field's two columns
|
|
32
|
+
const names = [...["id", "version", "created_at", "updated_at", "author_id", ...(s.publishing ? ["status", "published_at"] : [])], ...declared];
|
|
33
|
+
const required = new Set(s.schema.required ?? []), notNull = notNullColumns(s);
|
|
34
|
+
const nonNull = (column) => NATIVE_NOT_NULL.has(column) || notNull.has(column);
|
|
35
|
+
const ts = [...(s.publishing ? ["published_at"] : []), ...declared.filter(c => s.fields[c] === "timestamptz")], dates = declared.filter(c => s.fields[c] === "date");
|
|
36
|
+
const parent = s.translates && schemas[s.translates.parent.toLowerCase()];
|
|
37
|
+
const inherited = parent ? Object.keys(parent.fields).filter(c => c !== parent.scope && !Object.hasOwn(s.fields, c)).flatMap(c => parent.fields[c] === "geo" ? [`${c}_lat`, `${c}_lng`] : [c]) : [];
|
|
38
|
+
const inheritedType = (c) => parent?.fields[c] ?? "real";
|
|
39
|
+
const ref = refChecks(s, schemas);
|
|
40
|
+
const fact = {
|
|
41
|
+
...(s.userScopes?.length ? { u: s.userScopes.map(u => userScopeTerm(u, true)) } : {}),
|
|
42
|
+
...(s.scope ? { s: s.scope } : {}), ...(s.ttl ? { ttl: [s.ttl, s.ttlSeconds ?? 0] } : {}), ...(s.publishing ? { pub: 1 } : {}),
|
|
43
|
+
...(ts.length ? { ts } : {}), ...(dates.length ? { d: dates } : {}),
|
|
44
|
+
...(s.search?.length ? { search: s.search } : {}),
|
|
45
|
+
...(ref.length ? { ref } : {}),
|
|
46
|
+
...(parent ? { parent: { on: s.translates.on.toLowerCase(), ...(parent.scope ? { scope: [parent.scope, s.scope] } : {}), table: s.translates.parent.toLowerCase(), visible: rowPolicy(s, "caller", schemas).find(t => t[1] === "parent"), caller: rowPolicy(parent, "caller", schemas), trusted: rowPolicy(parent, "trusted", schemas), fields: inherited, ts: inherited.filter(c => parent.fields[c] === "timestamptz"), d: inherited.filter(c => parent.fields[c] === "date") } } : {}),
|
|
47
|
+
};
|
|
48
|
+
// the shared rules, with this Schema's scope and TTL put in, are Core's rules for it: or the runtime would enforce something else
|
|
49
|
+
const expected = q([core(s, schemas), writePolicy(s, "delete", { lock: true }).lock]), got = q([expand(rules, fact), rules.lock]);
|
|
50
|
+
if (expected !== got)
|
|
51
|
+
refuse(`schemas/${s.name}`, `Core's row rules for this Schema differ from the shared rules (${expected} vs ${got}); the generic Store cannot serve it.`);
|
|
52
|
+
// ORDER BY is allowed where the caller's scoped read can walk an index (its first column, or the one after the scope column) and the column is NOT NULL.
|
|
53
|
+
const indexed = new Set([...(s.publishing ? [[...(s.scope ? [s.scope] : []), "published_at", "id"]] : []), ...(s.indexes ?? []), ...(s.unique ?? [])].map(i => i[0] === s.scope ? i[1] : i[0]).filter((c) => !!c));
|
|
54
|
+
const key = (c) => /^[A-Za-z_]\w*$/.test(c) ? c : q(c);
|
|
55
|
+
const of = (columns, value) => `{ ${columns.map(c => `${key(c)}: ${value(c)};`).join(" ")} }`;
|
|
56
|
+
const union = (columns) => columns.map(c => q(c)).join(" | ") || "never";
|
|
57
|
+
const scalar = (c) => SCALAR.has(type(c));
|
|
58
|
+
const writable = declared.filter(c => !(s.userScopes ?? []).some(u => u.column === c) && (scalar(c) || type(c) === "json"));
|
|
59
|
+
const input = (c) => `${inputType(type(c))}${nonNull(c) ? "" : " | null"}`;
|
|
60
|
+
tables.push(`${key(s.name)}: { row: ${of([...names, ...geo], c => c === "id" ? "string" : nativeType(geo.includes(c) ? "real" : type(c), isSqlite, nonNull(c)))}${inherited.length ? ` & { ${inherited.map(c => `${key(c)}?: ${nativeType(inheritedType(c), isSqlite)};`).join(" ")} }` : ""}; fields: ${of(names.filter(scalar), c => inputType(type(c)))}; set: ${of(writable, input)}${s.userScopes?.length ? `; insert: ${of([...writable, ...s.userScopes.map(u => u.column)], input)}` : ""}; required: ${union(s.publishing ? [] : writable.filter(c => required.has(s.names?.[c] ?? c)))}; order: ${union(["id", ...names.filter(c => c !== "id" && scalar(c) && indexed.has(c) && (nonNull(c) || s.publishing && c === "published_at"))])} }`);
|
|
61
|
+
facts.push(`${q(s.name)}: ${q(fact)}`);
|
|
62
|
+
}
|
|
63
|
+
return { tables: `{ ${tables.join("; ")} }`, rules: q(rules), facts: `{ ${facts.join(", ")} }` };
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Generate-time tool versions: a stale host copy shows the model an older version than results carry. */
|
|
2
|
+
import { canonical, fnv } from "../build/compiler.js";
|
|
3
|
+
/** A hash of what a catalogue lists, so an unchanged definition keeps its version across deploys. */
|
|
4
|
+
export const versionOf = (value) => fnv(canonical(value));
|
|
5
|
+
/** A listed tool: its version, and its description (each locale's) ending with that version. */
|
|
6
|
+
export function versioned(tool) {
|
|
7
|
+
const v = versionOf(tool), mark = (text) => `${text} (tool version ${v})`;
|
|
8
|
+
return { ...tool, description: typeof tool.description === "string" ? mark(tool.description) : Object.fromEntries(Object.entries(tool.description).map(([locale, text]) => [locale, mark(text)])), version: v };
|
|
9
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { userReferences } from "../domain/userReferences.js";
|
|
2
|
+
/** Translation helpers use generated SQL policy, never a request-time manifest. */
|
|
3
|
+
import { rowPolicy } from "../build/compiler.js";
|
|
4
|
+
import { renderTerm } from "../kernel/store.js";
|
|
5
|
+
import { dialect as SQLITE } from "../kernel/dialect/sqlite.js";
|
|
6
|
+
import { dialect as POSTGRES } from "../kernel/dialect/postgres.js";
|
|
7
|
+
import { declaredType } from "../domain/columns.js";
|
|
8
|
+
import { driverFor, isSqlite, q } from "../domain/entry.js";
|
|
9
|
+
/** The parent is checked by the same atomic UPDATE that publishes the child. */
|
|
10
|
+
export function publishedParent(plan, child, d, recipes, trusted) {
|
|
11
|
+
const tr = child.translates, parent = plan.schemas[tr.parent.toLowerCase()], on = tr.on.toLowerCase();
|
|
12
|
+
const conditions = [`p.${d.ident(on)} = ${d.ident(child.name)}.${d.ident(on)}`];
|
|
13
|
+
if (parent.scope)
|
|
14
|
+
conditions.push(`p.${d.ident(parent.scope)} = ${d.ident(child.name)}.${d.ident(child.scope ?? parent.scope)}`);
|
|
15
|
+
if (parent.publishing)
|
|
16
|
+
conditions.push("p.status = 'published'");
|
|
17
|
+
for (const term of rowPolicy(parent, trusted ? "trusted" : "caller", plan.schemas))
|
|
18
|
+
conditions.push(renderTerm(d, term, "p.", recipe => (recipes.push(recipe), d.param(recipes.length))));
|
|
19
|
+
return `EXISTS (SELECT 1 FROM ${d.ident(parent.name)} p WHERE ${conditions.join(" AND ")})`;
|
|
20
|
+
}
|
|
21
|
+
export function emitTranslations(plan, target) {
|
|
22
|
+
const d = isSqlite(target) ? SQLITE : POSTGRES;
|
|
23
|
+
const user = userReferences(plan, isSqlite(target));
|
|
24
|
+
const children = Object.values(plan.schemas).filter(s => s.translates);
|
|
25
|
+
if (!Object.values(plan.schemas).some(s => s.localized))
|
|
26
|
+
return {};
|
|
27
|
+
if (!children.length)
|
|
28
|
+
return { "translations.ts": 'export {toCanonicalLocale} from "@aotter/mantle/translations";\n' };
|
|
29
|
+
const parents = [...new Set(children.map(s => s.translates.parent.toLowerCase()))], declarations = [], descriptors = [];
|
|
30
|
+
const driver = driverFor(target);
|
|
31
|
+
for (const parent of parents) {
|
|
32
|
+
const schema = plan.schemas[parent], types = children.filter(s => s.translates.parent.toLowerCase() === parent);
|
|
33
|
+
const entries = types.map(child => {
|
|
34
|
+
const on = child.translates.on.toLowerCase(), type = declaredType(child.fields, on);
|
|
35
|
+
const key = "t." + d.ident(on), projection = type === "timestamptz" ? d.wireTimestamp(key).join("") : type === "date" ? d.wireDate(key).join("") : key;
|
|
36
|
+
const lower = (mode) => {
|
|
37
|
+
const terms = rowPolicy(plan.schemas[child.name], mode, plan.schemas), recipes = [];
|
|
38
|
+
const where = terms.map(term => renderTerm(d, term, "t.", recipe => (recipes.push(recipe), d.param(recipes.length)))).join(" AND ");
|
|
39
|
+
return `{where:${q(where)},binds:${q(recipes)}}`;
|
|
40
|
+
};
|
|
41
|
+
return `{on:${q(on)},type:${q(type ?? "text")},sql:${q(`SELECT t.id AS _id, ${projection} AS _key, t.locale FROM ${d.ident(child.name)} t`)},caller:${lower('caller')},trusted:${lower('trusted')}}`;
|
|
42
|
+
});
|
|
43
|
+
const columns = [...new Set(types.map(s => s.translates.on.toLowerCase()))];
|
|
44
|
+
declarations.push(`${q(schema.name)}: Pick<Tables[${q(schema.name)}]["row"],${["id", ...columns.filter(c => c !== schema.scope)].map(value => q(value)).join("|")}>${schema.scope && columns.includes(schema.scope) ? ` & {readonly ${q(schema.scope)}?:string|null}` : ""};`);
|
|
45
|
+
descriptors.push(`${q(schema.name)}:[${entries.join(",")}]`);
|
|
46
|
+
}
|
|
47
|
+
return { "translations.ts": `${user.code ? user.code + "\n" : ""}import {ContractError,type Caller${driver === "postgres" ? ",type Unsafe as Database" : ""}} from "@aotter/mantle/contracts";
|
|
48
|
+
${driver !== "postgres" ? `import type {Handle as Database} from "@aotter/mantle/driver/${driver}";` : ""}
|
|
49
|
+
import {read} from "@aotter/mantle/driver/${driver}";
|
|
50
|
+
import {binds,encode} from "@aotter/mantle/binds";
|
|
51
|
+
import {param,ident${driver === "postgres" ? ",cast" : ""},timestamp,date} from "@aotter/mantle/dialect/${driver === "postgres" ? "postgres" : "sqlite"}";
|
|
52
|
+
import type {Tables} from "./generated.js";
|
|
53
|
+
export {toCanonicalLocale} from "@aotter/mantle/translations";
|
|
54
|
+
export interface TranslationParents {${declarations.join("\n")}}
|
|
55
|
+
const descriptors={${descriptors.join(",")}} as const;
|
|
56
|
+
/** Only pass the current parent page (up to 500); reads are bounded to its keys. */
|
|
57
|
+
export async function translationLocales<P extends keyof TranslationParents>(db:Database,caller:Caller,parent:P,parents:readonly TranslationParents[P][]):Promise<Record<string,string[]>> {
|
|
58
|
+
${user.used ? ' USER.modeOf(caller);\n' : ''} if(parents.length>500)throw new ContractError("INPUT_VALIDATION_FAILED","Pass one parent page, at most 500 rows.","translations");
|
|
59
|
+
const out:Record<string,string[]>=Object.fromEntries(parents.map(row=>[row.id,[]]));
|
|
60
|
+
for(const child of descriptors[parent]) {
|
|
61
|
+
const rows=parents as readonly Record<string,unknown>[],keys=[...new Set(rows.map(row=>row[child.on]).filter(value=>value!==null&&value!==undefined&&value!==""))];
|
|
62
|
+
const byKey=new Map<unknown,Set<string>>(),mode=${user.used ? 'USER.modeOf(caller)==="trusted"' : 'caller.kind==="system"'}?child.trusted:child.caller;
|
|
63
|
+
for(let i=0;i<keys.length;i+=50) {
|
|
64
|
+
const base=binds(${user.used ? "USER_VALUES" : "{}"},mode.binds,caller),batch=keys.slice(i,i+50);let after:string|undefined;
|
|
65
|
+
do {
|
|
66
|
+
const values=[...base,...batch.map(value=>encode({timestamp,date},child.type,value))],where=[...(mode.where?[${user.used ? "USER.scoped(mode.where,caller)" : "mode.where"}]:[]),"t."+ident(child.on)+" IN ("+batch.map((_,index)=>${driver === "postgres" ? "cast(param(base.length+index+1),child.type)" : "param(base.length+index+1)"}).join(",")+")"];
|
|
67
|
+
if(after!==undefined){values.push(after);where.push("t.id > "+param(values.length));}
|
|
68
|
+
const translations:Record<string,unknown>[]=await read(db,child.sql+" WHERE "+where.join(" AND ")+" ORDER BY t.id LIMIT 500",values);
|
|
69
|
+
for(const row of translations)if(typeof row.locale==="string")byKey.set(row._key,(byKey.get(row._key)??new Set()).add(row.locale));
|
|
70
|
+
after=translations.length===500?String(translations.at(-1)!._id):undefined;
|
|
71
|
+
}while(after!==undefined);
|
|
72
|
+
}
|
|
73
|
+
for(const row of rows)for(const locale of byKey.get(row[child.on])??[])if(!out[String(row.id)]!.includes(locale))out[String(row.id)]!.push(locale);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
` };
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { dialect as sqlite } from "../kernel/dialect/sqlite.js";
|
|
2
|
+
import { dialect as postgres } from "../kernel/dialect/postgres.js";
|
|
3
|
+
import { entry, exportName, q } from "../domain/entry.js";
|
|
4
|
+
export function emitTtl(plan, isSqlite, declarations, procedures, entries, refuse) {
|
|
5
|
+
const d = isSqlite ? sqlite : postgres;
|
|
6
|
+
const schemas = Object.values(plan.schemas).filter(s => s.ttl).sort((a, b) => a.name.localeCompare(b.name));
|
|
7
|
+
if (schemas.length)
|
|
8
|
+
declarations.push(`import { sweep as sweepTtl } from "@aotter/mantle/sweep";`, `import { cutoff as ttlCutoff } from "@aotter/mantle/dialect/${isSqlite ? "sqlite" : "postgres"}";`);
|
|
9
|
+
for (const s of schemas) {
|
|
10
|
+
const name = `${s.name}.sweepExpired`, fn = exportName("procedure", name), path = `procedures/${name}`;
|
|
11
|
+
if (plan.procedures[name]) {
|
|
12
|
+
refuse(path, "This name is reserved for generated TTL maintenance.");
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const pick = `SELECT _rid FROM ${d.ident(s.name)} WHERE ${d.ident(s.ttl)} IS NOT NULL AND ${d.ident(s.ttl)} <= ${d.param(1)} AND _rid > ${d.param(2)} ORDER BY _rid LIMIT ${d.param(3)}`;
|
|
16
|
+
declarations.push(`export type ${fn}Input = import("@aotter/mantle/sweep").SweepInput;`, `export type ${fn}Result = import("@aotter/mantle/sweep").SweepResult;`, `async function ${fn}(db: Database, caller: Caller, input: ${fn}Input = {}): Promise<${fn}Result> { return sweepTtl(db, caller, input, ${q(path)}, ${s.ttlSeconds}, ${q([pick, `DELETE FROM ${d.ident(s.name)} WHERE _rid IN (${pick}) RETURNING _rid`])}, {cutoff: ttlCutoff}, DRIVER.procedure); }`, `export { ${fn} };`);
|
|
17
|
+
procedures.push(`[${q(name)}]: ${fn}`);
|
|
18
|
+
entries[`procedures/${encodeURIComponent(name)}.ts`] = entry("procedure", name, `${fn}Input as Input, ${fn}Result as Output`);
|
|
19
|
+
}
|
|
20
|
+
}
|