@aotter/mantle 0.2.0-alpha.5 → 0.2.0-alpha.7
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 +152 -45
- package/dist/auth/adapter.d.ts +32 -0
- package/dist/auth/adapter.js +285 -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 +154 -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 +31 -0
- package/dist/auth/oauth.js +124 -0
- package/dist/auth/resource-seeds.js +10 -0
- package/dist/auth/routes.d.ts +32 -0
- package/dist/auth/{createAuthRoutes.js → routes.js} +130 -35
- package/dist/build/compiler.d.ts +790 -0
- package/dist/build/compiler.js +6971 -0
- package/dist/{spec → build/spec}/domain/model/ManifestGrammar.d.ts +28 -42
- package/dist/{spec → build/spec}/domain/model/RuntimePlan.d.ts +77 -3
- package/dist/{spec → build/spec}/domain/model/SqlIr.d.ts +35 -6
- package/dist/build/spec/domain/model/UserFields.d.ts +20 -0
- package/dist/{spec → build/spec}/domain/service/PlanFingerprint.d.ts +2 -1
- package/dist/{spec → build/spec}/kernel/diagnostic.d.ts +1 -30
- 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 +55 -0
- package/dist/domain/describe/chunk-ZIQVLA7X.js +363 -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 +17 -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 +204 -0
- package/dist/domain/recipes.js +19 -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 +84 -0
- package/dist/infrastructure/cli.js +101 -0
- package/dist/infrastructure/generate.d.ts +14 -0
- package/dist/infrastructure/generate.js +174 -0
- package/dist/infrastructure/generateAuth.d.ts +8 -0
- package/dist/infrastructure/generateAuth.js +17 -0
- package/dist/infrastructure/inputs.js +36 -0
- package/dist/infrastructure/wrangler.js +71 -0
- package/dist/kernel/authorization.d.ts +4 -0
- package/dist/kernel/authorization.js +16 -0
- package/dist/kernel/binds.d.ts +51 -0
- package/dist/kernel/binds.js +45 -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 +139 -0
- package/dist/kernel/contracts.js +92 -0
- package/dist/{spec/domain/service/CloudflareCron.d.ts → kernel/cron.d.ts} +0 -1
- package/dist/{spec/domain/service/CloudflareCron.js → kernel/cron.js} +1 -6
- package/dist/kernel/dialect/common.d.ts +39 -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/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 +95 -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/{auth/emailTemplates.js → kernel/email.js} +13 -2
- package/dist/kernel/host-access.d.ts +10 -0
- package/dist/kernel/host-access.js +65 -0
- package/dist/kernel/host-caller.d.ts +38 -0
- package/dist/kernel/host-caller.js +55 -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 +49 -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 +288 -0
- package/dist/kernel/mcp.d.ts +101 -0
- package/dist/kernel/mcp.js +25 -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/{cloudflare/r2Media.d.ts → kernel/r2-media.d.ts} +4 -5
- package/dist/kernel/r2-media.js +78 -0
- package/dist/kernel/rest-dispatch.js +38 -0
- package/dist/kernel/rest-router.js +63 -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 +43 -0
- package/dist/kernel/search.js +5 -0
- package/dist/{core/site.d.ts → kernel/site-types.d.ts} +38 -9
- package/dist/kernel/site-types.js +1 -0
- package/dist/kernel/site.d.ts +41 -0
- package/dist/kernel/site.js +32 -0
- package/dist/kernel/store.d.ts +165 -0
- package/dist/kernel/store.js +257 -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 +34 -0
- package/dist/kernel/user-fields.js +71 -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-response.js +8 -0
- package/dist/kernel/web.d.ts +75 -0
- package/dist/kernel/web.js +155 -0
- package/dist/kernel/where.js +27 -0
- package/dist/testing/index.d.ts +1 -37
- package/dist/testing/index.js +1 -43
- package/dist/usecase/adminBindings.js +142 -0
- package/dist/usecase/adminDescriptors.js +153 -0
- package/dist/usecase/generate.js +164 -0
- package/dist/usecase/handlers.js +21 -0
- package/dist/usecase/mcp-media.js +17 -0
- package/dist/usecase/mcp.js +96 -0
- package/dist/usecase/paging.js +72 -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 +40 -0
- package/dist/usecase/stores.js +62 -0
- package/dist/usecase/translations.js +78 -0
- package/dist/usecase/ttl.js +20 -0
- package/dist/usecase/validation.js +132 -0
- package/dist/usecase/web.js +22 -0
- package/docs/examples/README.md +31 -16
- package/docs/examples/app-shell/README.md +48 -0
- package/docs/examples/app-shell/package.json +34 -0
- package/docs/examples/app-shell/pnpm-workspace.yaml +8 -0
- package/docs/examples/app-shell/smoke-kit.mjs +100 -0
- package/docs/examples/app-shell/src/admin.ts +10 -0
- package/docs/examples/app-shell/src/auth.ts +55 -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 +13 -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/{reference-service → app-shell}/tsconfig.json +2 -1
- package/docs/examples/app-shell/worker-configuration.d.ts +22 -0
- package/docs/examples/app-shell/wrangler.jsonc +61 -0
- package/docs/examples/club-events/README.md +60 -0
- package/docs/examples/club-events/manifests/club-events.yaml +225 -0
- package/docs/examples/club-events/smoke.mjs +121 -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 +74 -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 +7 -0
- package/docs/examples/purchasing/.dev.vars.example +6 -0
- package/docs/examples/purchasing/README.md +90 -0
- package/docs/examples/purchasing/manifests/purchasing.yaml +341 -0
- package/docs/examples/purchasing/seed.sql +4 -0
- package/docs/examples/purchasing/smoke.mjs +171 -0
- package/docs/examples/purchasing/src/admin.ts +12 -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 +68 -0
- package/docs/examples/recipes/README.md +58 -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 +73 -0
- package/docs/handbook/cloudflare/authentication.md +142 -212
- package/docs/handbook/cloudflare/chatgpt-sites.md +4 -113
- package/docs/handbook/cloudflare/deploy-and-operate.md +20 -103
- package/docs/handbook/cloudflare/media-r2.md +62 -133
- package/docs/handbook/cloudflare/service-entry.md +180 -116
- package/docs/handbook/concepts/authorization.md +10 -123
- package/docs/handbook/concepts/four-atoms.md +9 -92
- package/docs/handbook/concepts/lifecycle-and-locales.md +9 -80
- package/docs/handbook/concepts/mcp-and-agents.md +10 -141
- package/docs/handbook/concepts/procedures-and-triggers.md +5 -162
- package/docs/handbook/concepts/runtime-and-adapters.md +15 -183
- package/docs/handbook/concepts/views.md +10 -109
- package/docs/handbook/examples/hub.md +2 -18
- package/docs/handbook/guides/adapter-guide.md +11 -0
- package/docs/handbook/guides/admin-extensions.md +20 -0
- package/docs/handbook/guides/admin-ui.md +5 -163
- package/docs/handbook/guides/agent-setup.md +5 -18
- package/docs/handbook/guides/email.md +86 -0
- package/docs/handbook/guides/mcp-apps.md +44 -0
- package/docs/handbook/guides/native-admin.md +298 -0
- package/docs/handbook/guides/native-execution.md +132 -0
- package/docs/handbook/guides/oauth.md +105 -0
- package/docs/handbook/guides/postgres-adapters.md +56 -0
- package/docs/handbook/guides/public-pages.md +53 -0
- package/docs/handbook/guides/size-and-startup.md +9 -0
- package/docs/handbook/guides/typed-queries.md +39 -102
- package/docs/handbook/navigation.json +164 -30
- package/docs/handbook/reference/admin-descriptors.md +88 -0
- package/docs/handbook/reference/authorization.md +44 -52
- package/docs/handbook/reference/describe.md +56 -0
- package/docs/handbook/reference/diagnostics.md +15 -78
- package/docs/handbook/reference/features.md +45 -53
- package/docs/handbook/reference/manifest.md +6 -69
- package/docs/handbook/reference/procedure.md +9 -117
- package/docs/handbook/reference/schema.md +65 -172
- package/docs/handbook/reference/site-config.md +5 -65
- package/docs/handbook/reference/surface.md +279 -93
- package/docs/handbook/reference/trigger.md +24 -93
- package/docs/handbook/reference/view.md +32 -158
- package/docs/handbook/releases/index.md +124 -15
- package/docs/handbook/start/overview.md +9 -56
- package/docs/handbook/start/project-and-cli.md +40 -141
- package/docs/handbook/start/quickstart-worker.md +38 -116
- package/docs/skills/develop/SKILL.md +34 -117
- package/docs/skills/develop/mcp-app.md +37 -77
- package/docs/skills/integrate/SKILL.md +42 -84
- package/docs/skills/media-gc/SKILL.md +14 -3
- package/docs/skills/provision/SKILL.md +25 -88
- package/docs/skills/update/SKILL.md +19 -40
- package/docs/source/scripts/postgres-adapter.ts +101 -0
- package/docs/upgrade-0.1-to-0.2.md +26 -10
- package/package.json +97 -112
- package/skills/.keep +0 -0
- package/skills/README.md +72 -0
- package/skills/mantle/SKILL.md +135 -0
- package/skills/mantle/references/local-testing.md +11 -0
- package/skills/mantle/references/web.md +41 -0
- package/skills/mantle/references/website.md +47 -0
- package/dist/admin/consent.d.ts +0 -17
- package/dist/admin/consent.d.ts.map +0 -1
- package/dist/admin/consent.js +0 -17
- package/dist/admin/consent.js.map +0 -1
- package/dist/admin/createAdminSurface.d.ts +0 -22
- package/dist/admin/createAdminSurface.d.ts.map +0 -1
- package/dist/admin/createAdminSurface.js +0 -592
- package/dist/admin/createAdminSurface.js.map +0 -1
- package/dist/admin/design.d.ts +0 -7
- package/dist/admin/design.d.ts.map +0 -1
- package/dist/admin/design.js +0 -18
- package/dist/admin/design.js.map +0 -1
- package/dist/admin/developerConsole.d.ts +0 -267
- package/dist/admin/developerConsole.d.ts.map +0 -1
- package/dist/admin/developerConsole.js +0 -136
- package/dist/admin/developerConsole.js.map +0 -1
- package/dist/admin/identity.d.ts +0 -62
- package/dist/admin/identity.d.ts.map +0 -1
- package/dist/admin/identity.js +0 -7
- package/dist/admin/identity.js.map +0 -1
- package/dist/admin/index.d.ts +0 -6
- package/dist/admin/index.d.ts.map +0 -1
- package/dist/admin/index.js +0 -6
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/procedureFlow.d.ts +0 -38
- package/dist/admin/procedureFlow.d.ts.map +0 -1
- package/dist/admin/procedureFlow.js +0 -188
- package/dist/admin/procedureFlow.js.map +0 -1
- package/dist/auth/ConsoleEmailSender.d.ts +0 -15
- package/dist/auth/ConsoleEmailSender.d.ts.map +0 -1
- package/dist/auth/ConsoleEmailSender.js +0 -19
- package/dist/auth/ConsoleEmailSender.js.map +0 -1
- package/dist/auth/appleClientSecret.d.ts +0 -75
- package/dist/auth/appleClientSecret.d.ts.map +0 -1
- package/dist/auth/appleClientSecret.js +0 -116
- package/dist/auth/appleClientSecret.js.map +0 -1
- package/dist/auth/buildAuth.d.ts +0 -4566
- package/dist/auth/buildAuth.d.ts.map +0 -1
- package/dist/auth/buildAuth.js +0 -311
- package/dist/auth/buildAuth.js.map +0 -1
- package/dist/auth/callerResolver.d.ts +0 -48
- package/dist/auth/callerResolver.d.ts.map +0 -1
- package/dist/auth/callerResolver.js +0 -49
- package/dist/auth/callerResolver.js.map +0 -1
- package/dist/auth/createAuthRoutes.d.ts +0 -22
- package/dist/auth/createAuthRoutes.d.ts.map +0 -1
- package/dist/auth/createAuthRoutes.js.map +0 -1
- package/dist/auth/createMantleAuth.d.ts +0 -3
- package/dist/auth/createMantleAuth.d.ts.map +0 -1
- package/dist/auth/createMantleAuth.js +0 -503
- package/dist/auth/createMantleAuth.js.map +0 -1
- package/dist/auth/db.d.ts +0 -17
- package/dist/auth/db.d.ts.map +0 -1
- package/dist/auth/db.js +0 -24
- package/dist/auth/db.js.map +0 -1
- package/dist/auth/emailTemplates.d.ts +0 -7
- package/dist/auth/emailTemplates.d.ts.map +0 -1
- package/dist/auth/emailTemplates.js.map +0 -1
- package/dist/auth/index.d.ts +0 -9
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -7
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/methods.d.ts +0 -717
- package/dist/auth/methods.d.ts.map +0 -1
- package/dist/auth/methods.js +0 -321
- package/dist/auth/methods.js.map +0 -1
- package/dist/auth/oauthTokens.d.ts +0 -24
- package/dist/auth/oauthTokens.d.ts.map +0 -1
- package/dist/auth/oauthTokens.js +0 -146
- package/dist/auth/oauthTokens.js.map +0 -1
- package/dist/auth/setupIncomplete.d.ts +0 -16
- package/dist/auth/setupIncomplete.d.ts.map +0 -1
- package/dist/auth/setupIncomplete.js +0 -50
- package/dist/auth/setupIncomplete.js.map +0 -1
- package/dist/auth/types.d.ts +0 -440
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js +0 -10
- package/dist/auth/types.js.map +0 -1
- package/dist/bun/index.d.ts +0 -65
- package/dist/bun/index.d.ts.map +0 -1
- package/dist/bun/index.js +0 -130
- package/dist/bun/index.js.map +0 -1
- package/dist/cli/emitModule.d.ts +0 -7
- package/dist/cli/emitModule.d.ts.map +0 -1
- package/dist/cli/emitModule.js +0 -127
- package/dist/cli/emitModule.js.map +0 -1
- package/dist/cli/generate.d.ts +0 -8
- package/dist/cli/generate.d.ts.map +0 -1
- package/dist/cli/generate.js +0 -391
- package/dist/cli/generate.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 -21
- package/dist/cli/main.js.map +0 -1
- package/dist/cli/preset.d.ts +0 -18
- package/dist/cli/preset.d.ts.map +0 -1
- package/dist/cli/preset.js +0 -353
- package/dist/cli/preset.js.map +0 -1
- package/dist/cloudflare/index.d.ts +0 -21
- package/dist/cloudflare/index.d.ts.map +0 -1
- package/dist/cloudflare/index.js +0 -14
- package/dist/cloudflare/index.js.map +0 -1
- package/dist/cloudflare/r2Media.d.ts.map +0 -1
- package/dist/cloudflare/r2Media.js +0 -69
- package/dist/cloudflare/r2Media.js.map +0 -1
- package/dist/core/caller.d.ts +0 -35
- package/dist/core/caller.d.ts.map +0 -1
- package/dist/core/caller.js +0 -3
- package/dist/core/caller.js.map +0 -1
- package/dist/core/createMantle.d.ts +0 -22
- package/dist/core/createMantle.d.ts.map +0 -1
- package/dist/core/createMantle.js +0 -117
- package/dist/core/createMantle.js.map +0 -1
- package/dist/core/dialect.d.ts +0 -41
- package/dist/core/dialect.d.ts.map +0 -1
- package/dist/core/dialect.js +0 -7
- package/dist/core/dialect.js.map +0 -1
- package/dist/core/driver.d.ts +0 -17
- package/dist/core/driver.d.ts.map +0 -1
- package/dist/core/driver.js +0 -2
- package/dist/core/driver.js.map +0 -1
- package/dist/core/email.d.ts +0 -17
- package/dist/core/email.d.ts.map +0 -1
- package/dist/core/email.js +0 -2
- package/dist/core/email.js.map +0 -1
- package/dist/core/index.d.ts +0 -16
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -16
- package/dist/core/index.js.map +0 -1
- package/dist/core/invocation.d.ts +0 -66
- package/dist/core/invocation.d.ts.map +0 -1
- package/dist/core/invocation.js +0 -3
- package/dist/core/invocation.js.map +0 -1
- package/dist/core/observation.d.ts +0 -3
- package/dist/core/observation.d.ts.map +0 -1
- package/dist/core/observation.js +0 -10
- package/dist/core/observation.js.map +0 -1
- package/dist/core/runtime/auth.d.ts +0 -9
- package/dist/core/runtime/auth.d.ts.map +0 -1
- package/dist/core/runtime/auth.js +0 -39
- package/dist/core/runtime/auth.js.map +0 -1
- package/dist/core/runtime/createRuntime.d.ts +0 -23
- package/dist/core/runtime/createRuntime.d.ts.map +0 -1
- package/dist/core/runtime/createRuntime.js +0 -214
- package/dist/core/runtime/createRuntime.js.map +0 -1
- package/dist/core/runtime/verifyPlan.d.ts +0 -34
- package/dist/core/runtime/verifyPlan.d.ts.map +0 -1
- package/dist/core/runtime/verifyPlan.js +0 -301
- package/dist/core/runtime/verifyPlan.js.map +0 -1
- package/dist/core/service.d.ts +0 -45
- package/dist/core/service.d.ts.map +0 -1
- package/dist/core/service.js +0 -2
- package/dist/core/service.js.map +0 -1
- package/dist/core/site.d.ts.map +0 -1
- package/dist/core/site.js +0 -2
- package/dist/core/site.js.map +0 -1
- package/dist/core/siteConfig.d.ts +0 -5
- package/dist/core/siteConfig.d.ts.map +0 -1
- package/dist/core/siteConfig.js +0 -24
- package/dist/core/siteConfig.js.map +0 -1
- package/dist/core/sql/allowlist.d.ts +0 -65
- package/dist/core/sql/allowlist.d.ts.map +0 -1
- package/dist/core/sql/allowlist.js +0 -705
- package/dist/core/sql/allowlist.js.map +0 -1
- package/dist/core/sql/ast.d.ts +0 -24
- package/dist/core/sql/ast.d.ts.map +0 -1
- package/dist/core/sql/ast.js +0 -32
- package/dist/core/sql/ast.js.map +0 -1
- package/dist/core/sql/compile.d.ts +0 -50
- package/dist/core/sql/compile.d.ts.map +0 -1
- package/dist/core/sql/compile.js +0 -48
- package/dist/core/sql/compile.js.map +0 -1
- package/dist/core/sql/policy.d.ts +0 -121
- package/dist/core/sql/policy.d.ts.map +0 -1
- package/dist/core/sql/policy.js +0 -379
- package/dist/core/sql/policy.js.map +0 -1
- package/dist/core/sql/positions.d.ts +0 -30
- package/dist/core/sql/positions.d.ts.map +0 -1
- package/dist/core/sql/positions.js +0 -22
- package/dist/core/sql/positions.js.map +0 -1
- package/dist/core/sql/run.d.ts +0 -76
- package/dist/core/sql/run.d.ts.map +0 -1
- package/dist/core/sql/run.js +0 -267
- package/dist/core/sql/run.js.map +0 -1
- package/dist/core/store/createStore.d.ts +0 -49
- package/dist/core/store/createStore.d.ts.map +0 -1
- package/dist/core/store/createStore.js +0 -227
- package/dist/core/store/createStore.js.map +0 -1
- package/dist/core/store/cursor.d.ts +0 -4
- package/dist/core/store/cursor.d.ts.map +0 -1
- package/dist/core/store/cursor.js +0 -22
- package/dist/core/store/cursor.js.map +0 -1
- package/dist/core/store/json.d.ts +0 -80
- package/dist/core/store/json.d.ts.map +0 -1
- package/dist/core/store/json.js +0 -342
- package/dist/core/store/json.js.map +0 -1
- package/dist/core/store.d.ts +0 -160
- package/dist/core/store.d.ts.map +0 -1
- package/dist/core/store.js +0 -2
- package/dist/core/store.js.map +0 -1
- package/dist/core/wire.d.ts +0 -20
- package/dist/core/wire.d.ts.map +0 -1
- package/dist/core/wire.js +0 -84
- package/dist/core/wire.js.map +0 -1
- package/dist/core/withCaller.d.ts +0 -24
- package/dist/core/withCaller.d.ts.map +0 -1
- package/dist/core/withCaller.js +0 -35
- package/dist/core/withCaller.js.map +0 -1
- package/dist/d1/adapter.d.ts +0 -12
- package/dist/d1/adapter.d.ts.map +0 -1
- package/dist/d1/adapter.js +0 -23
- package/dist/d1/adapter.js.map +0 -1
- package/dist/d1/codec.d.ts +0 -14
- package/dist/d1/codec.d.ts.map +0 -1
- package/dist/d1/codec.js +0 -136
- package/dist/d1/codec.js.map +0 -1
- package/dist/d1/compile/index.d.ts +0 -9
- package/dist/d1/compile/index.d.ts.map +0 -1
- package/dist/d1/compile/index.js +0 -9
- package/dist/d1/compile/index.js.map +0 -1
- package/dist/d1/dialect.d.ts +0 -4
- package/dist/d1/dialect.d.ts.map +0 -1
- package/dist/d1/dialect.js +0 -13
- package/dist/d1/dialect.js.map +0 -1
- package/dist/d1/executor.d.ts +0 -13
- package/dist/d1/executor.d.ts.map +0 -1
- package/dist/d1/executor.js +0 -91
- package/dist/d1/executor.js.map +0 -1
- package/dist/d1/index.d.ts +0 -6
- package/dist/d1/index.d.ts.map +0 -1
- package/dist/d1/index.js +0 -6
- package/dist/d1/index.js.map +0 -1
- package/dist/d1/lower.d.ts +0 -8
- package/dist/d1/lower.d.ts.map +0 -1
- package/dist/d1/lower.js +0 -167
- package/dist/d1/lower.js.map +0 -1
- package/dist/d1/media.d.ts +0 -5
- package/dist/d1/media.d.ts.map +0 -1
- package/dist/d1/media.js +0 -157
- package/dist/d1/media.js.map +0 -1
- package/dist/d1/migrations.d.ts +0 -12
- package/dist/d1/migrations.d.ts.map +0 -1
- package/dist/d1/migrations.js +0 -111
- package/dist/d1/migrations.js.map +0 -1
- package/dist/d1/print.d.ts +0 -28
- package/dist/d1/print.d.ts.map +0 -1
- package/dist/d1/print.js +0 -54
- package/dist/d1/print.js.map +0 -1
- package/dist/d1/site.d.ts +0 -17
- package/dist/d1/site.d.ts.map +0 -1
- package/dist/d1/site.js +0 -63
- package/dist/d1/site.js.map +0 -1
- package/dist/d1/storage.d.ts +0 -34
- package/dist/d1/storage.d.ts.map +0 -1
- package/dist/d1/storage.js +0 -288
- package/dist/d1/storage.js.map +0 -1
- package/dist/d1/tz.d.ts +0 -21
- package/dist/d1/tz.d.ts.map +0 -1
- package/dist/d1/tz.js +0 -54
- package/dist/d1/tz.js.map +0 -1
- package/dist/d1/validator.d.ts +0 -10
- package/dist/d1/validator.d.ts.map +0 -1
- package/dist/d1/validator.js +0 -33
- package/dist/d1/validator.js.map +0 -1
- package/dist/mcp/apps.d.ts +0 -43
- package/dist/mcp/apps.d.ts.map +0 -1
- package/dist/mcp/apps.js +0 -57
- package/dist/mcp/apps.js.map +0 -1
- package/dist/mcp/catalog.d.ts +0 -55
- package/dist/mcp/catalog.d.ts.map +0 -1
- package/dist/mcp/catalog.js +0 -81
- package/dist/mcp/catalog.js.map +0 -1
- package/dist/mcp/createMcpSurface.d.ts +0 -60
- package/dist/mcp/createMcpSurface.d.ts.map +0 -1
- package/dist/mcp/createMcpSurface.js +0 -182
- package/dist/mcp/createMcpSurface.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -4
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/media.d.ts +0 -9
- package/dist/mcp/media.d.ts.map +0 -1
- package/dist/mcp/media.js +0 -33
- package/dist/mcp/media.js.map +0 -1
- package/dist/postgres/adapter.d.ts +0 -20
- package/dist/postgres/adapter.d.ts.map +0 -1
- package/dist/postgres/adapter.js +0 -44
- package/dist/postgres/adapter.js.map +0 -1
- package/dist/postgres/codec.d.ts +0 -13
- package/dist/postgres/codec.d.ts.map +0 -1
- package/dist/postgres/codec.js +0 -110
- package/dist/postgres/codec.js.map +0 -1
- package/dist/postgres/compile/index.d.ts +0 -9
- package/dist/postgres/compile/index.d.ts.map +0 -1
- package/dist/postgres/compile/index.js +0 -9
- package/dist/postgres/compile/index.js.map +0 -1
- package/dist/postgres/driver.d.ts +0 -92
- package/dist/postgres/driver.d.ts.map +0 -1
- package/dist/postgres/driver.js +0 -110
- package/dist/postgres/driver.js.map +0 -1
- package/dist/postgres/executor.d.ts +0 -20
- package/dist/postgres/executor.d.ts.map +0 -1
- package/dist/postgres/executor.js +0 -69
- package/dist/postgres/executor.js.map +0 -1
- package/dist/postgres/index.d.ts +0 -7
- package/dist/postgres/index.d.ts.map +0 -1
- package/dist/postgres/index.js +0 -7
- package/dist/postgres/index.js.map +0 -1
- package/dist/postgres/lower.d.ts +0 -6
- package/dist/postgres/lower.d.ts.map +0 -1
- package/dist/postgres/lower.js +0 -157
- package/dist/postgres/lower.js.map +0 -1
- package/dist/postgres/print.d.ts +0 -11
- package/dist/postgres/print.d.ts.map +0 -1
- package/dist/postgres/print.js +0 -121
- package/dist/postgres/print.js.map +0 -1
- package/dist/postgres/storage.d.ts +0 -19
- package/dist/postgres/storage.d.ts.map +0 -1
- package/dist/postgres/storage.js +0 -201
- package/dist/postgres/storage.js.map +0 -1
- package/dist/postgres/textRows.d.ts +0 -10
- package/dist/postgres/textRows.d.ts.map +0 -1
- package/dist/postgres/textRows.js +0 -44
- package/dist/postgres/textRows.js.map +0 -1
- package/dist/postgres/validator.d.ts +0 -12
- package/dist/postgres/validator.d.ts.map +0 -1
- package/dist/postgres/validator.js +0 -46
- package/dist/postgres/validator.js.map +0 -1
- package/dist/spec/domain/index.d.ts +0 -3
- package/dist/spec/domain/index.d.ts.map +0 -1
- package/dist/spec/domain/index.js +0 -3
- package/dist/spec/domain/index.js.map +0 -1
- package/dist/spec/domain/model/ContentState.d.ts +0 -8
- package/dist/spec/domain/model/ContentState.d.ts.map +0 -1
- package/dist/spec/domain/model/ContentState.js +0 -7
- package/dist/spec/domain/model/ContentState.js.map +0 -1
- package/dist/spec/domain/model/Entry.d.ts +0 -29
- package/dist/spec/domain/model/Entry.d.ts.map +0 -1
- package/dist/spec/domain/model/Entry.js +0 -16
- package/dist/spec/domain/model/Entry.js.map +0 -1
- package/dist/spec/domain/model/Locale.d.ts +0 -30
- package/dist/spec/domain/model/Locale.d.ts.map +0 -1
- package/dist/spec/domain/model/Locale.js +0 -33
- package/dist/spec/domain/model/Locale.js.map +0 -1
- package/dist/spec/domain/model/ManifestGrammar.d.ts.map +0 -1
- package/dist/spec/domain/model/ManifestGrammar.js +0 -149
- package/dist/spec/domain/model/ManifestGrammar.js.map +0 -1
- package/dist/spec/domain/model/MediaMimeAccept.d.ts +0 -55
- package/dist/spec/domain/model/MediaMimeAccept.d.ts.map +0 -1
- package/dist/spec/domain/model/MediaMimeAccept.js +0 -90
- package/dist/spec/domain/model/MediaMimeAccept.js.map +0 -1
- package/dist/spec/domain/model/RuntimePlan.d.ts.map +0 -1
- package/dist/spec/domain/model/RuntimePlan.js +0 -4
- package/dist/spec/domain/model/RuntimePlan.js.map +0 -1
- package/dist/spec/domain/model/SiteConfig.d.ts +0 -173
- package/dist/spec/domain/model/SiteConfig.d.ts.map +0 -1
- package/dist/spec/domain/model/SiteConfig.js +0 -13
- package/dist/spec/domain/model/SiteConfig.js.map +0 -1
- package/dist/spec/domain/model/SqlIr.d.ts.map +0 -1
- package/dist/spec/domain/model/SqlIr.js +0 -95
- package/dist/spec/domain/model/SqlIr.js.map +0 -1
- package/dist/spec/domain/model/index.d.ts +0 -14
- package/dist/spec/domain/model/index.d.ts.map +0 -1
- package/dist/spec/domain/model/index.js +0 -14
- package/dist/spec/domain/model/index.js.map +0 -1
- package/dist/spec/domain/service/CloudflareCron.d.ts.map +0 -1
- package/dist/spec/domain/service/CloudflareCron.js.map +0 -1
- package/dist/spec/domain/service/CrossSchemaChecker.d.ts +0 -34
- package/dist/spec/domain/service/CrossSchemaChecker.d.ts.map +0 -1
- package/dist/spec/domain/service/CrossSchemaChecker.js +0 -114
- package/dist/spec/domain/service/CrossSchemaChecker.js.map +0 -1
- package/dist/spec/domain/service/EntryDataValidator.d.ts +0 -56
- package/dist/spec/domain/service/EntryDataValidator.d.ts.map +0 -1
- package/dist/spec/domain/service/EntryDataValidator.js +0 -88
- package/dist/spec/domain/service/EntryDataValidator.js.map +0 -1
- package/dist/spec/domain/service/JsonSchemaToZod.d.ts +0 -29
- package/dist/spec/domain/service/JsonSchemaToZod.d.ts.map +0 -1
- package/dist/spec/domain/service/JsonSchemaToZod.js +0 -156
- package/dist/spec/domain/service/JsonSchemaToZod.js.map +0 -1
- package/dist/spec/domain/service/LifecycleStateMachine.d.ts +0 -75
- package/dist/spec/domain/service/LifecycleStateMachine.d.ts.map +0 -1
- package/dist/spec/domain/service/LifecycleStateMachine.js +0 -76
- package/dist/spec/domain/service/LifecycleStateMachine.js.map +0 -1
- package/dist/spec/domain/service/LocaleCanonicalizer.d.ts +0 -29
- package/dist/spec/domain/service/LocaleCanonicalizer.d.ts.map +0 -1
- package/dist/spec/domain/service/LocaleCanonicalizer.js +0 -96
- package/dist/spec/domain/service/LocaleCanonicalizer.js.map +0 -1
- package/dist/spec/domain/service/ManifestFieldChecks.d.ts +0 -60
- package/dist/spec/domain/service/ManifestFieldChecks.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestFieldChecks.js +0 -116
- package/dist/spec/domain/service/ManifestFieldChecks.js.map +0 -1
- package/dist/spec/domain/service/ManifestGraphValidator.d.ts +0 -15
- package/dist/spec/domain/service/ManifestGraphValidator.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestGraphValidator.js +0 -225
- package/dist/spec/domain/service/ManifestGraphValidator.js.map +0 -1
- package/dist/spec/domain/service/ManifestLinker.d.ts +0 -45
- package/dist/spec/domain/service/ManifestLinker.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestLinker.js +0 -92
- package/dist/spec/domain/service/ManifestLinker.js.map +0 -1
- package/dist/spec/domain/service/ManifestLocaleTrimmer.d.ts +0 -16
- package/dist/spec/domain/service/ManifestLocaleTrimmer.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestLocaleTrimmer.js +0 -76
- package/dist/spec/domain/service/ManifestLocaleTrimmer.js.map +0 -1
- package/dist/spec/domain/service/ManifestParser.d.ts +0 -49
- package/dist/spec/domain/service/ManifestParser.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestParser.js +0 -203
- package/dist/spec/domain/service/ManifestParser.js.map +0 -1
- package/dist/spec/domain/service/ManifestPartition.d.ts +0 -9
- package/dist/spec/domain/service/ManifestPartition.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestPartition.js +0 -19
- package/dist/spec/domain/service/ManifestPartition.js.map +0 -1
- package/dist/spec/domain/service/ManifestPathDiagnoser.d.ts +0 -33
- package/dist/spec/domain/service/ManifestPathDiagnoser.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestPathDiagnoser.js +0 -43
- package/dist/spec/domain/service/ManifestPathDiagnoser.js.map +0 -1
- package/dist/spec/domain/service/ManifestSourceSpans.d.ts +0 -10
- package/dist/spec/domain/service/ManifestSourceSpans.d.ts.map +0 -1
- package/dist/spec/domain/service/ManifestSourceSpans.js +0 -76
- package/dist/spec/domain/service/ManifestSourceSpans.js.map +0 -1
- package/dist/spec/domain/service/McpToolNaming.d.ts +0 -25
- package/dist/spec/domain/service/McpToolNaming.d.ts.map +0 -1
- package/dist/spec/domain/service/McpToolNaming.js +0 -50
- package/dist/spec/domain/service/McpToolNaming.js.map +0 -1
- package/dist/spec/domain/service/PlanFingerprint.d.ts.map +0 -1
- package/dist/spec/domain/service/PlanFingerprint.js +0 -14
- package/dist/spec/domain/service/PlanFingerprint.js.map +0 -1
- package/dist/spec/domain/service/ProcedureViewSpecChecks.d.ts +0 -4
- package/dist/spec/domain/service/ProcedureViewSpecChecks.d.ts.map +0 -1
- package/dist/spec/domain/service/ProcedureViewSpecChecks.js +0 -211
- package/dist/spec/domain/service/ProcedureViewSpecChecks.js.map +0 -1
- package/dist/spec/domain/service/SchemaAdminUiChecker.d.ts +0 -42
- package/dist/spec/domain/service/SchemaAdminUiChecker.d.ts.map +0 -1
- package/dist/spec/domain/service/SchemaAdminUiChecker.js +0 -289
- package/dist/spec/domain/service/SchemaAdminUiChecker.js.map +0 -1
- package/dist/spec/domain/service/SchemaIndexChecker.d.ts +0 -33
- package/dist/spec/domain/service/SchemaIndexChecker.d.ts.map +0 -1
- package/dist/spec/domain/service/SchemaIndexChecker.js +0 -256
- package/dist/spec/domain/service/SchemaIndexChecker.js.map +0 -1
- package/dist/spec/domain/service/SchemaSearchChecker.d.ts +0 -11
- package/dist/spec/domain/service/SchemaSearchChecker.d.ts.map +0 -1
- package/dist/spec/domain/service/SchemaSearchChecker.js +0 -78
- package/dist/spec/domain/service/SchemaSearchChecker.js.map +0 -1
- package/dist/spec/domain/service/SchemaSpecChecks.d.ts +0 -31
- package/dist/spec/domain/service/SchemaSpecChecks.d.ts.map +0 -1
- package/dist/spec/domain/service/SchemaSpecChecks.js +0 -445
- package/dist/spec/domain/service/SchemaSpecChecks.js.map +0 -1
- package/dist/spec/domain/service/SiteDefaultsValidator.d.ts +0 -39
- package/dist/spec/domain/service/SiteDefaultsValidator.d.ts.map +0 -1
- package/dist/spec/domain/service/SiteDefaultsValidator.js +0 -194
- package/dist/spec/domain/service/SiteDefaultsValidator.js.map +0 -1
- package/dist/spec/domain/service/SqlClassify.d.ts +0 -19
- package/dist/spec/domain/service/SqlClassify.d.ts.map +0 -1
- package/dist/spec/domain/service/SqlClassify.js +0 -42
- package/dist/spec/domain/service/SqlClassify.js.map +0 -1
- package/dist/spec/domain/service/SqlRefusal.d.ts +0 -16
- package/dist/spec/domain/service/SqlRefusal.d.ts.map +0 -1
- package/dist/spec/domain/service/SqlRefusal.js +0 -20
- package/dist/spec/domain/service/SqlRefusal.js.map +0 -1
- package/dist/spec/domain/service/SqlTypes.d.ts +0 -40
- package/dist/spec/domain/service/SqlTypes.d.ts.map +0 -1
- package/dist/spec/domain/service/SqlTypes.js +0 -110
- package/dist/spec/domain/service/SqlTypes.js.map +0 -1
- package/dist/spec/domain/service/StaffRoleHierarchy.d.ts +0 -3
- package/dist/spec/domain/service/StaffRoleHierarchy.d.ts.map +0 -1
- package/dist/spec/domain/service/StaffRoleHierarchy.js +0 -19
- package/dist/spec/domain/service/StaffRoleHierarchy.js.map +0 -1
- package/dist/spec/domain/service/TriggerGraphChecks.d.ts +0 -16
- package/dist/spec/domain/service/TriggerGraphChecks.d.ts.map +0 -1
- package/dist/spec/domain/service/TriggerGraphChecks.js +0 -337
- package/dist/spec/domain/service/TriggerGraphChecks.js.map +0 -1
- package/dist/spec/domain/service/TriggerSpecChecks.d.ts +0 -4
- package/dist/spec/domain/service/TriggerSpecChecks.d.ts.map +0 -1
- package/dist/spec/domain/service/TriggerSpecChecks.js +0 -89
- package/dist/spec/domain/service/TriggerSpecChecks.js.map +0 -1
- package/dist/spec/domain/service/ValidatedAdminUiProjection.d.ts +0 -11
- package/dist/spec/domain/service/ValidatedAdminUiProjection.d.ts.map +0 -1
- package/dist/spec/domain/service/ValidatedAdminUiProjection.js +0 -26
- package/dist/spec/domain/service/ValidatedAdminUiProjection.js.map +0 -1
- package/dist/spec/domain/service/ViewAdminUiChecker.d.ts +0 -14
- package/dist/spec/domain/service/ViewAdminUiChecker.d.ts.map +0 -1
- package/dist/spec/domain/service/ViewAdminUiChecker.js +0 -53
- package/dist/spec/domain/service/ViewAdminUiChecker.js.map +0 -1
- package/dist/spec/domain/service/index.d.ts +0 -24
- package/dist/spec/domain/service/index.d.ts.map +0 -1
- package/dist/spec/domain/service/index.js +0 -24
- package/dist/spec/domain/service/index.js.map +0 -1
- package/dist/spec/index.d.ts +0 -10
- package/dist/spec/index.d.ts.map +0 -1
- package/dist/spec/index.js +0 -12
- package/dist/spec/index.js.map +0 -1
- package/dist/spec/infrastructure/cli/parseArgsError.d.ts +0 -16
- package/dist/spec/infrastructure/cli/parseArgsError.d.ts.map +0 -1
- package/dist/spec/infrastructure/cli/parseArgsError.js +0 -34
- package/dist/spec/infrastructure/cli/parseArgsError.js.map +0 -1
- package/dist/spec/infrastructure/index.d.ts +0 -2
- package/dist/spec/infrastructure/index.d.ts.map +0 -1
- package/dist/spec/infrastructure/index.js +0 -2
- package/dist/spec/infrastructure/index.js.map +0 -1
- package/dist/spec/infrastructure/sql/PgQueryParser.d.ts +0 -18
- package/dist/spec/infrastructure/sql/PgQueryParser.d.ts.map +0 -1
- package/dist/spec/infrastructure/sql/PgQueryParser.js +0 -17
- package/dist/spec/infrastructure/sql/PgQueryParser.js.map +0 -1
- package/dist/spec/infrastructure/sql/compilePlan.d.ts +0 -37
- package/dist/spec/infrastructure/sql/compilePlan.d.ts.map +0 -1
- package/dist/spec/infrastructure/sql/compilePlan.js +0 -282
- package/dist/spec/infrastructure/sql/compilePlan.js.map +0 -1
- package/dist/spec/infrastructure/sql/compileSql.d.ts +0 -35
- package/dist/spec/infrastructure/sql/compileSql.d.ts.map +0 -1
- package/dist/spec/infrastructure/sql/compileSql.js +0 -92
- package/dist/spec/infrastructure/sql/compileSql.js.map +0 -1
- package/dist/spec/infrastructure/sql/frontEnd.d.ts +0 -8
- package/dist/spec/infrastructure/sql/frontEnd.d.ts.map +0 -1
- package/dist/spec/infrastructure/sql/frontEnd.js +0 -100
- package/dist/spec/infrastructure/sql/frontEnd.js.map +0 -1
- package/dist/spec/infrastructure/sql/index.d.ts +0 -4
- package/dist/spec/infrastructure/sql/index.d.ts.map +0 -1
- package/dist/spec/infrastructure/sql/index.js +0 -4
- package/dist/spec/infrastructure/sql/index.js.map +0 -1
- package/dist/spec/kernel/diagnostic.d.ts.map +0 -1
- package/dist/spec/kernel/diagnostic.js +0 -296
- package/dist/spec/kernel/diagnostic.js.map +0 -1
- package/dist/spec/kernel/index.d.ts +0 -8
- package/dist/spec/kernel/index.d.ts.map +0 -1
- package/dist/spec/kernel/index.js +0 -8
- package/dist/spec/kernel/index.js.map +0 -1
- package/dist/spec/usecase/EmitTypes.d.ts +0 -22
- package/dist/spec/usecase/EmitTypes.d.ts.map +0 -1
- package/dist/spec/usecase/EmitTypes.js +0 -182
- package/dist/spec/usecase/EmitTypes.js.map +0 -1
- package/dist/spec/usecase/ValidateManifestsUseCase.d.ts +0 -8
- package/dist/spec/usecase/ValidateManifestsUseCase.d.ts.map +0 -1
- package/dist/spec/usecase/ValidateManifestsUseCase.js +0 -101
- package/dist/spec/usecase/ValidateManifestsUseCase.js.map +0 -1
- package/dist/spec/usecase/dto/ValidateManifestsRequest.d.ts +0 -27
- package/dist/spec/usecase/dto/ValidateManifestsRequest.d.ts.map +0 -1
- package/dist/spec/usecase/dto/ValidateManifestsRequest.js +0 -2
- package/dist/spec/usecase/dto/ValidateManifestsRequest.js.map +0 -1
- package/dist/spec/usecase/dto/ValidateManifestsResponse.d.ts +0 -14
- package/dist/spec/usecase/dto/ValidateManifestsResponse.d.ts.map +0 -1
- package/dist/spec/usecase/dto/ValidateManifestsResponse.js +0 -2
- package/dist/spec/usecase/dto/ValidateManifestsResponse.js.map +0 -1
- package/dist/spec/usecase/dto/index.d.ts +0 -3
- package/dist/spec/usecase/dto/index.d.ts.map +0 -1
- package/dist/spec/usecase/dto/index.js +0 -3
- package/dist/spec/usecase/dto/index.js.map +0 -1
- package/dist/spec/usecase/index.d.ts +0 -10
- package/dist/spec/usecase/index.d.ts.map +0 -1
- package/dist/spec/usecase/index.js +0 -10
- package/dist/spec/usecase/index.js.map +0 -1
- package/dist/testing/cases/before-hook.d.ts +0 -4
- package/dist/testing/cases/before-hook.d.ts.map +0 -1
- package/dist/testing/cases/before-hook.js +0 -71
- package/dist/testing/cases/before-hook.js.map +0 -1
- package/dist/testing/cases/corpus.d.ts +0 -12
- package/dist/testing/cases/corpus.d.ts.map +0 -1
- package/dist/testing/cases/corpus.js +0 -91
- package/dist/testing/cases/corpus.js.map +0 -1
- package/dist/testing/cases/policy.d.ts +0 -74
- package/dist/testing/cases/policy.d.ts.map +0 -1
- package/dist/testing/cases/policy.js +0 -142
- package/dist/testing/cases/policy.js.map +0 -1
- package/dist/testing/cases/printer.d.ts +0 -4
- package/dist/testing/cases/printer.d.ts.map +0 -1
- package/dist/testing/cases/printer.js +0 -32
- package/dist/testing/cases/printer.js.map +0 -1
- package/dist/testing/cases/report-view.d.ts +0 -5
- package/dist/testing/cases/report-view.d.ts.map +0 -1
- package/dist/testing/cases/report-view.js +0 -33
- package/dist/testing/cases/report-view.js.map +0 -1
- package/dist/testing/cases/requisition.d.ts +0 -4
- package/dist/testing/cases/requisition.d.ts.map +0 -1
- package/dist/testing/cases/requisition.js +0 -38
- package/dist/testing/cases/requisition.js.map +0 -1
- package/dist/testing/cases/search-places.d.ts +0 -4
- package/dist/testing/cases/search-places.d.ts.map +0 -1
- package/dist/testing/cases/search-places.js +0 -60
- package/dist/testing/cases/search-places.js.map +0 -1
- package/dist/testing/cases/stock.d.ts +0 -4
- package/dist/testing/cases/stock.d.ts.map +0 -1
- package/dist/testing/cases/stock.js +0 -35
- package/dist/testing/cases/stock.js.map +0 -1
- package/dist/testing/cases/store.d.ts +0 -4
- package/dist/testing/cases/store.d.ts.map +0 -1
- package/dist/testing/cases/store.js +0 -100
- package/dist/testing/cases/store.js.map +0 -1
- package/dist/testing/harness.d.ts +0 -94
- package/dist/testing/harness.d.ts.map +0 -1
- package/dist/testing/harness.js +0 -168
- package/dist/testing/harness.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/report.d.ts +0 -15
- package/dist/testing/report.d.ts.map +0 -1
- package/dist/testing/report.js +0 -22
- package/dist/testing/report.js.map +0 -1
- package/dist/web/createRestSurface.d.ts +0 -7
- package/dist/web/createRestSurface.d.ts.map +0 -1
- package/dist/web/createRestSurface.js +0 -41
- package/dist/web/createRestSurface.js.map +0 -1
- package/dist/web/index.d.ts +0 -2
- package/dist/web/index.d.ts.map +0 -1
- package/dist/web/index.js +0 -2
- package/dist/web/index.js.map +0 -1
- 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 -939
- 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 -244
- 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/0029-mcp-apps-interaction-contracts.md +0 -324
- package/docs/adr/0030-store.md +0 -40
- package/docs/adr/0031-host-cloud-core-contract.md +0 -102
- package/docs/adr/0032-store-centric-core-and-service-contract.md +0 -384
- package/docs/adr/0033-storage-converges-to-the-plan.md +0 -92
- package/docs/adr/0034-store-is-authored-as-sql.md +0 -192
- package/docs/adr/0035-sql-dialects.md +0 -154
- package/docs/adr/0036-host-and-dialect.md +0 -65
- package/docs/adr/0037-postgresql-is-the-reference-dialect.md +0 -124
- package/docs/adr/0038-bun-native-host.md +0 -63
- package/docs/adr/README.md +0 -96
- 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 -48
- package/docs/adr/adr-lite-909-admin-ui-kit.md +0 -37
- package/docs/examples/commerce-inventory.md +0 -481
- package/docs/examples/commerce.md +0 -254
- package/docs/examples/guarded-api.md +0 -238
- package/docs/examples/intake-hooks.md +0 -237
- package/docs/examples/intake.md +0 -127
- package/docs/examples/legal-documents.md +0 -218
- package/docs/examples/procurement.md +0 -226
- package/docs/examples/publication.md +0 -211
- package/docs/examples/reference-service/.mantle/generated/mantle.ts +0 -174
- package/docs/examples/reference-service/.mantle/generated/plan.json +0 -1936
- package/docs/examples/reference-service/README.md +0 -33
- package/docs/examples/reference-service/manifests/catalog.yaml +0 -103
- package/docs/examples/reference-service/manifests/orders.yaml +0 -120
- package/docs/examples/reference-service/mantle.config.json +0 -1
- package/docs/examples/reference-service/package.json +0 -27
- package/docs/examples/reference-service/smoke.mjs +0 -160
- package/docs/examples/reference-service/src/handlers.ts +0 -36
- package/docs/examples/reference-service/src/index.ts +0 -33
- package/docs/examples/reference-service/src/service.ts +0 -78
- package/docs/examples/reference-service/wrangler.jsonc +0 -26
- package/docs/examples/reservation.md +0 -142
- /package/docs/examples/{reference-service → app-shell}/.dev.vars.example +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ConsoleEmailSender, type EmailSender } from "@aotter/mantle/email";
|
|
2
|
+
import { env } from "cloudflare:workers";
|
|
3
|
+
|
|
4
|
+
// AGENT: replace with a real sender before deploying (Cloudflare `send_email` binding, docs/handbook/guides/email.md).
|
|
5
|
+
// ConsoleEmailSender only logs; it sends nothing.
|
|
6
|
+
export const sender: EmailSender = new ConsoleEmailSender();
|
|
7
|
+
|
|
8
|
+
// Recipients come from Better Auth through Mantle's server-side read (no session, no SQL on Better Auth's tables).
|
|
9
|
+
// Loaded lazily: Better Auth stays out of the Worker's startup graph.
|
|
10
|
+
const identity = async () => (await import("@aotter/mantle/auth/identity")).readIdentity((await import("./auth.js")).createAuth(env));
|
|
11
|
+
|
|
12
|
+
/** Active admins (Better Auth adminRoles / adminUserIds). */
|
|
13
|
+
export async function adminEmails(): Promise<string[]> {
|
|
14
|
+
return (await (await identity()).admins()).map(user => user.email);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Email failures never undo a decision that has already committed. */
|
|
18
|
+
export async function notify(to: string, subject: string, text: string, category: string) {
|
|
19
|
+
try { await sender.send({ to, subject, text, locale: "en", category }); }
|
|
20
|
+
catch (error) { console.error(`[purchasing] ${category} email failed`, error); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Looks up the user's email and sends; a failed lookup is logged like a failed send. */
|
|
24
|
+
export async function notifyUser(userId: string, subject: string, text: string, category: string) {
|
|
25
|
+
let to: string | undefined;
|
|
26
|
+
try { to = (await (await identity()).user(userId))?.email; }
|
|
27
|
+
catch (error) { console.error(`[purchasing] ${category} recipient lookup failed`, error); }
|
|
28
|
+
if (to) await notify(to, subject, text, category);
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Caller } from "@aotter/mantle/contracts";
|
|
2
|
+
import { store } from "../generated/generated.js";
|
|
3
|
+
import myRequests from "../generated/paged/views/my-requests.js";
|
|
4
|
+
import { handlers } from "./handlers.js";
|
|
5
|
+
import { signInRedirect } from "./sign-in.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /requests: the employee's own page. Anonymous visitors go to /sign-in and come back.
|
|
9
|
+
* Reads run here as the signed-in caller (`my-requests` restricts rows to `author_id = auth.uid()`). Writes do not:
|
|
10
|
+
* the form submits to `POST /api/requests`, the REST Trigger the manifest declares for `submit-request`, so input
|
|
11
|
+
* validation, `requires` and the origin check happen there. Request data never reaches a Procedure through app code.
|
|
12
|
+
* AGENT: unstyled on purpose; add the app's styling, copy and a change-amount action (its own Trigger) as needed.
|
|
13
|
+
*/
|
|
14
|
+
export async function requestsPage(request: Request, env: Env, caller: Caller): Promise<Response> {
|
|
15
|
+
if (caller.kind === "anonymous") return signInRedirect(request);
|
|
16
|
+
const mine = await myRequests(env.DB, caller, {}, { limit: 50 });
|
|
17
|
+
// Department names only; the budget amounts stay admin-side.
|
|
18
|
+
const budgets = await store(env.DB, { kind: "system", reason: "department list" }, { handlers }).collection("budgets").find({ limit: 100 });
|
|
19
|
+
return new Response(`<!doctype html>
|
|
20
|
+
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
21
|
+
<meta name="robots" content="noindex"><title>My purchase requests</title>
|
|
22
|
+
<h1>My purchase requests</h1>
|
|
23
|
+
<form action="/api/requests" method="post" id="new-request">
|
|
24
|
+
<label>What <input name="title" required></label>
|
|
25
|
+
<label>Amount <input name="amount" type="number" min="1" step="1" required></label>
|
|
26
|
+
<label>Department <select name="department" required>${budgets.rows.map(b => `<option>${escape(String(b.department))}</option>`).join("")}</select></label>
|
|
27
|
+
<button>Submit</button>
|
|
28
|
+
<p role="alert" id="error" hidden></p>
|
|
29
|
+
</form>
|
|
30
|
+
<table>
|
|
31
|
+
<tr><th>What</th><th>Amount</th><th>Department</th><th>Status</th></tr>
|
|
32
|
+
${mine.rows.map(r => `<tr><td>${escape(String(r.title))}</td><td>${escape(String(r.amount))}</td><td>${escape(String(r.department))}</td><td>${escape(String(r.requestStatus))}</td></tr>`).join("\n ")}
|
|
33
|
+
</table>
|
|
34
|
+
<script type="module">
|
|
35
|
+
// The submit event fires only after the browser's own checks (required, min) pass; the Trigger checks again.
|
|
36
|
+
const form = document.getElementById("new-request"), error = document.getElementById("error");
|
|
37
|
+
form.addEventListener("submit", async event => {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
// Field types come from the inputs, so a number input sends a JSON number.
|
|
40
|
+
const body = Object.fromEntries([...form.elements].filter(field => field.name).map(field => [field.name, field.type === "number" ? field.valueAsNumber : field.value]));
|
|
41
|
+
const button = form.querySelector("button");
|
|
42
|
+
button.disabled = true;
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(form.action, { method: form.method, headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
45
|
+
if (response.ok) return location.reload(); // the page reads the list again
|
|
46
|
+
error.textContent = (await response.json().catch(() => null))?.error?.message ?? "Not submitted. Try again.";
|
|
47
|
+
} catch {
|
|
48
|
+
error.textContent = "Not submitted: the network is unavailable.";
|
|
49
|
+
}
|
|
50
|
+
error.hidden = false;
|
|
51
|
+
button.disabled = false;
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
`, { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" } });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const escape = (text: string) => text.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ContractError, type Caller } from "@aotter/mantle/contracts";
|
|
2
|
+
import { store } from "../generated/generated.js";
|
|
3
|
+
import confirmOrder from "../generated/procedures/confirm-order.js";
|
|
4
|
+
import { handlers } from "./handlers.js";
|
|
5
|
+
import { requestsPage } from "./requests-page.js";
|
|
6
|
+
import { signInPage } from "./sign-in.js";
|
|
7
|
+
|
|
8
|
+
const MAX_BODY = 64 * 1024, MAX_SKEW_SECONDS = 300;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* App-owned routes, tried before Admin, MCP and REST. Return undefined to fall through.
|
|
12
|
+
* GET /sign-in: member sign-in (src/sign-in.ts). GET /requests: the employee's page (src/requests-page.ts).
|
|
13
|
+
* POST /callbacks/supplier: the supplier confirms an order. The signature is checked over the RAW body before parsing.
|
|
14
|
+
* AGENT: match the real supplier's signature scheme (header names, what is signed, encoding).
|
|
15
|
+
*/
|
|
16
|
+
export async function appRoutes(request: Request, env: Env, context: { caller: () => Promise<Caller>; waitUntil: (task: Promise<unknown>) => void }): Promise<Response | undefined> {
|
|
17
|
+
const path = new URL(request.url).pathname;
|
|
18
|
+
if (path === "/sign-in" && request.method === "GET") return signInPage();
|
|
19
|
+
if (path === "/requests" && request.method === "GET") return requestsPage(request, env, await context.caller());
|
|
20
|
+
if (path !== "/callbacks/supplier") return undefined;
|
|
21
|
+
if (request.method !== "POST") return new Response(null, { status: 405, headers: { allow: "POST" } });
|
|
22
|
+
if (Number(request.headers.get("content-length") ?? MAX_BODY + 1) > MAX_BODY) return new Response(null, { status: 413 });
|
|
23
|
+
const raw = await request.arrayBuffer();
|
|
24
|
+
const timestamp = request.headers.get("x-supplier-timestamp") ?? "";
|
|
25
|
+
if (!(await verified(env.SUPPLIER_WEBHOOK_SECRET, timestamp, raw, request.headers.get("x-supplier-signature") ?? "")))
|
|
26
|
+
return new Response(null, { status: 401 });
|
|
27
|
+
|
|
28
|
+
let body: { reference?: unknown; orderId?: unknown };
|
|
29
|
+
try { body = JSON.parse(new TextDecoder().decode(raw)); } catch { return new Response(null, { status: 400 }); }
|
|
30
|
+
if (typeof body.reference !== "string" || typeof body.orderId !== "string") return new Response(null, { status: 400 });
|
|
31
|
+
// Trusted path: a system caller, after the signature proved the sender.
|
|
32
|
+
const system: Caller = { kind: "system", reason: "supplier callback" };
|
|
33
|
+
try {
|
|
34
|
+
await confirmOrder(env.DB, system, { id: body.reference, supplierOrderId: body.orderId });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (!(error instanceof ContractError && error.code === "CONFLICT")) throw error;
|
|
37
|
+
// A replay of a callback already applied succeeds again, so the supplier stops retrying.
|
|
38
|
+
const current = await store(env.DB, system, { handlers }).collection("requests").findById(body.reference);
|
|
39
|
+
if (current?.requeststatus === "confirmed" && current.supplierorderid === body.orderId) return new Response(null, { status: 204 });
|
|
40
|
+
return new Response(null, { status: 409 }); // unknown request, another order id, or not approved/ordered
|
|
41
|
+
}
|
|
42
|
+
return new Response(null, { status: 204 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** HMAC-SHA256 over `${timestamp}.${raw body}`, hex encoded; crypto.subtle.verify compares in constant time. */
|
|
46
|
+
async function verified(secret: string, timestamp: string, raw: ArrayBuffer, signature: string) {
|
|
47
|
+
if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > MAX_SKEW_SECONDS) return false;
|
|
48
|
+
if (!/^[0-9a-f]{64}$/.test(signature)) return false;
|
|
49
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
|
|
50
|
+
const signed = new Uint8Array([...new TextEncoder().encode(`${timestamp}.`), ...new Uint8Array(raw)]);
|
|
51
|
+
const bytes = new Uint8Array(signature.match(/../g)!.map(pair => parseInt(pair, 16)));
|
|
52
|
+
return crypto.subtle.verify("HMAC", key, bytes, signed);
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { toCloudflareCron } from "@aotter/mantle/cron";
|
|
2
|
+
import type { Caller } from "@aotter/mantle/contracts";
|
|
3
|
+
import { store } from "../generated/generated.js";
|
|
4
|
+
import approvedRequests from "../generated/views/approved-requests.js";
|
|
5
|
+
import staleRequests from "../generated/views/stale-requests.js";
|
|
6
|
+
import { handlers } from "./handlers.js";
|
|
7
|
+
import { adminEmails, notify } from "./notify.js";
|
|
8
|
+
import { placeOrder } from "./supplier.js";
|
|
9
|
+
|
|
10
|
+
/** Daily 09:00 UTC; list the same expression in wrangler.jsonc `triggers.crons`. */
|
|
11
|
+
export const DAILY = toCloudflareCron("0 9 * * *");
|
|
12
|
+
/** Remind about requests still 'submitted' after this many days. */
|
|
13
|
+
const PENDING_DAYS = 3;
|
|
14
|
+
|
|
15
|
+
export async function scheduled(controller: ScheduledController, env: Env): Promise<void> {
|
|
16
|
+
if (controller.cron !== DAILY) throw new Error(`No application schedule runs on '${controller.cron}'`);
|
|
17
|
+
const system: Caller = { kind: "system", reason: "daily purchasing cron" };
|
|
18
|
+
|
|
19
|
+
// 1. Retry supplier orders that failed right after approval.
|
|
20
|
+
const data = store(env.DB, system, { handlers });
|
|
21
|
+
for (const { id, title, amount } of await approvedRequests(env.DB, system, {}))
|
|
22
|
+
await placeOrder(data, { id: String(id), title: String(title), amount: Number(amount) }); // View rows are typed loosely
|
|
23
|
+
|
|
24
|
+
// 2. Remind admins about requests waiting too long.
|
|
25
|
+
const before = new Date(controller.scheduledTime - PENDING_DAYS * 86_400_000).toISOString();
|
|
26
|
+
const stale = await staleRequests(env.DB, system, { before });
|
|
27
|
+
if (stale.length === 0) return;
|
|
28
|
+
const text = `${stale.length} purchase request(s) have waited more than ${PENDING_DAYS} days:\n` +
|
|
29
|
+
stale.map(r => `- ${r.title} (${r.department}, ${r.amount})`).join("\n");
|
|
30
|
+
for (const to of await adminEmails()) await notify(to, "Purchase requests waiting for approval", text, "purchasing.reminder");
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Employees only: an email may create an account only on the company's domain (ADMIN_EMAIL always signs in). */
|
|
2
|
+
export async function allowSignUp(env: Env, email: string): Promise<boolean> {
|
|
3
|
+
// AGENT: set EMPLOYEE_DOMAIN in wrangler.jsonc vars; for a per-person list, see the club-events example.
|
|
4
|
+
return email.toLowerCase().endsWith(`@${env.EMPLOYEE_DOMAIN.toLowerCase()}`);
|
|
5
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { env } from "cloudflare:workers";
|
|
2
|
+
import { ContractError } from "@aotter/mantle/contracts";
|
|
3
|
+
import type { HandlerContext } from "../generated/generated.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Orders an approved request from the supplier, then marks it 'ordered'. Returns false when the supplier could not be
|
|
7
|
+
* reached; the request stays 'approved' and the daily cron (src/schedule.ts) retries it.
|
|
8
|
+
* AGENT: use the supplier's real API, auth and payload.
|
|
9
|
+
*/
|
|
10
|
+
export async function placeOrder(store: HandlerContext["store"], request: { id: string; title: string; amount: number }) {
|
|
11
|
+
let orderId: string;
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(env.SUPPLIER_API_URL, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
// The request id as idempotency key: a retry after a lost response does not order twice.
|
|
16
|
+
headers: { "content-type": "application/json", "idempotency-key": request.id },
|
|
17
|
+
body: JSON.stringify({ reference: request.id, item: request.title, amount: request.amount }),
|
|
18
|
+
signal: AbortSignal.timeout(10_000),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) throw new Error(`supplier answered ${response.status}`);
|
|
21
|
+
({ orderId } = await response.json() as { orderId: string });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`[purchasing] supplier order for ${request.id} failed; it stays approved for the retry`, error);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
try { await store.procedures["mark-ordered"]({ id: request.id, supplierOrderId: orderId }); }
|
|
27
|
+
catch (error) {
|
|
28
|
+
// The supplier's callback already confirmed it (it can arrive before this write).
|
|
29
|
+
if (!(error instanceof ContractError && error.code === "CONFLICT")) throw error;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "mantle-app",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"compatibility_date": "2026-09-01",
|
|
6
|
+
"compatibility_flags": [
|
|
7
|
+
"nodejs_compat"
|
|
8
|
+
],
|
|
9
|
+
"vars": {
|
|
10
|
+
"EMPLOYEE_DOMAIN": "example.com"
|
|
11
|
+
},
|
|
12
|
+
"secrets": {
|
|
13
|
+
"required": [
|
|
14
|
+
"BETTER_AUTH_SECRET",
|
|
15
|
+
"PUBLIC_ORIGIN",
|
|
16
|
+
"ADMIN_EMAIL",
|
|
17
|
+
"SUPPLIER_API_URL",
|
|
18
|
+
"SUPPLIER_WEBHOOK_SECRET"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"observability": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"logs": {
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"head_sampling_rate": 1
|
|
26
|
+
},
|
|
27
|
+
"traces": {
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"head_sampling_rate": 0.01
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"d1_databases": [
|
|
33
|
+
{
|
|
34
|
+
"binding": "DB",
|
|
35
|
+
"database_name": "mantle-app"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"assets": {
|
|
39
|
+
"directory": "node_modules/@aotter/mantle-admin/dist/admin",
|
|
40
|
+
"binding": "ASSETS",
|
|
41
|
+
"run_worker_first": true,
|
|
42
|
+
"html_handling": "none"
|
|
43
|
+
},
|
|
44
|
+
"triggers": {
|
|
45
|
+
"crons": [
|
|
46
|
+
"0 9 * * *"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"no_bundle": true,
|
|
50
|
+
"find_additional_modules": true,
|
|
51
|
+
"base_dir": "dist",
|
|
52
|
+
"build": {
|
|
53
|
+
"command": "mantle bundle src/index.ts dist",
|
|
54
|
+
"watch_dir": [
|
|
55
|
+
"src",
|
|
56
|
+
"generated"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"rules": [
|
|
60
|
+
{
|
|
61
|
+
"type": "ESModule",
|
|
62
|
+
"globs": [
|
|
63
|
+
"**/*.js"
|
|
64
|
+
],
|
|
65
|
+
"fallthrough": true
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
A multilingual recipe site. Admins write recipes in Admin as drafts and publish them; visitors read the published
|
|
4
|
+
recipes in Traditional Chinese (`zh-TW`, the default) and English (`en`) without signing in. The visitor pages are
|
|
5
|
+
plain app-owned HTML with the head tags search and answer engines read, plus Markdown and `llms.txt` for AI agents.
|
|
6
|
+
|
|
7
|
+
## Capabilities shown
|
|
8
|
+
|
|
9
|
+
- Publishing lifecycle (draft → publish): `lifecycle: publishing` on both Schemas in `manifests/recipes.yaml`;
|
|
10
|
+
Admin's publish action runs the generated `recipes.publish` Procedure. Public Views and anonymous Store reads see
|
|
11
|
+
published rows only.
|
|
12
|
+
- Translations: `recipe_translations` is `localized` and `translates: { parent: recipes, on: slug }`. A translation
|
|
13
|
+
publishes only while its parent is published, and its reads merge the parent's `total_minutes`, `servings` and
|
|
14
|
+
`cover_url` (`src/routes.ts` `read`).
|
|
15
|
+
- Visitor pages in `src/routes.ts` (routing, reads) and `src/pages.ts` (rendering), always read as an anonymous caller:
|
|
16
|
+
- every item of [public pages](../../handbook/guides/public-pages.md): head tags, Open Graph, JSON-LD `Recipe`, the
|
|
17
|
+
short answer block, hreflang and `x-default`, per-language Markdown alternates, `llms.txt`, sitemap and robots.
|
|
18
|
+
- Cover images on R2: `wrangler.jsonc` (the shell's file plus the `MEDIA` bucket and two vars), `src/site-config.ts`
|
|
19
|
+
(media purpose `cover`) and `src/media.ts` (`r2MediaStorage`). See "Cover images" below to switch it on.
|
|
20
|
+
|
|
21
|
+
Search for `AGENT:` for what an app must still provide: layout and styling, cache policy, list paging, the S3 signer.
|
|
22
|
+
|
|
23
|
+
## Combine with
|
|
24
|
+
|
|
25
|
+
- Need members to see or edit only their own rows? Add club-events' `restrict: { recipes: "author_id = auth.uid()" }` on a View.
|
|
26
|
+
- Need review before publishing, or an AI that approves? Add purchasing's guarded status transitions and admin MCP.
|
|
27
|
+
|
|
28
|
+
## Copy
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
cp -R node_modules/@aotter/mantle/docs/examples/app-shell/. my-app/
|
|
32
|
+
cp -R node_modules/@aotter/mantle/docs/examples/recipes/. my-app/
|
|
33
|
+
cd my-app && pnpm install && pnpm generate && pnpm types && pnpm check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then follow the shell README (`db:init`, `dev`). Sign in at `/admin`, add a recipe, publish it, open `/zh-TW/`.
|
|
37
|
+
Add a translation from the recipe's language tab and publish it to get `/en/`.
|
|
38
|
+
|
|
39
|
+
## Cover images
|
|
40
|
+
|
|
41
|
+
The media library needs the generated site module, which the shell does not generate. To switch it on:
|
|
42
|
+
|
|
43
|
+
1. In `package.json`, add `--site src/site-config.ts` to both the `generate` and `check` commands, then
|
|
44
|
+
`pnpm generate` (this adds the `site_config`/`media_assets` tables to `generated/schema.sql`).
|
|
45
|
+
2. In `src/service.ts`, import `site` from `../generated/site.js` and `coverStorage` from `./media.js`, and add
|
|
46
|
+
`site, media: coverStorage(env, signer)` to `adminOptions`. The signer is app-owned (see `src/media.ts`).
|
|
47
|
+
3. Create the bucket, set CORS and the two `vars` in `wrangler.jsonc` (see `docs/handbook/cloudflare/media-r2.md`).
|
|
48
|
+
|
|
49
|
+
Admin's media library then uploads straight to R2 with presigned URLs; copy a committed image's URL into `cover_url`.
|
|
50
|
+
The smoke does not cover upload: local `wrangler dev` has an R2 binding but no S3 endpoint to presign against.
|
|
51
|
+
|
|
52
|
+
## `pnpm smoke` proves
|
|
53
|
+
|
|
54
|
+
- Drafts (zh-TW and en) are absent from detail, `.md`, list and `llms.txt`, even when the request has an admin session.
|
|
55
|
+
- A translation cannot publish before its parent; publishing both through the Admin API makes them visible.
|
|
56
|
+
- Each locale's detail page has title, description, canonical, hreflang (`zh-TW`, `en`, `x-default`) and a switcher link.
|
|
57
|
+
- JSON-LD parses as a `Recipe` with the recipe name; the en page inherits `totalTime` from its parent; the short answer is present.
|
|
58
|
+
- `.md` returns `text/markdown`; `llms.txt` and both locale lists show the published recipe; `/` redirects to `/zh-TW/`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Recipes. The parent row is the default locale (zh-TW); recipe_translations holds each other locale (en, ...).
|
|
2
|
+
# Both use the publishing lifecycle: Admin creates drafts, publish makes them visible to visitors.
|
|
3
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
4
|
+
kind: Schema
|
|
5
|
+
metadata: {name: recipes}
|
|
6
|
+
spec:
|
|
7
|
+
title: Recipes
|
|
8
|
+
lifecycle: publishing
|
|
9
|
+
uniqueIndexes: [[slug]]
|
|
10
|
+
searchableFields: [title]
|
|
11
|
+
schema:
|
|
12
|
+
type: object
|
|
13
|
+
required: [slug, title, summary, ingredients, steps, total_minutes, servings]
|
|
14
|
+
properties:
|
|
15
|
+
slug: {type: string, pattern: "^[a-z0-9-]+$", maxLength: 80}
|
|
16
|
+
title: {type: string}
|
|
17
|
+
summary: {type: string, description: "One or two sentences; used as the meta description."}
|
|
18
|
+
ingredients: {type: string, description: "One ingredient per line."}
|
|
19
|
+
steps: {type: string, description: "One step per line."}
|
|
20
|
+
total_minutes: {type: integer, minimum: 1}
|
|
21
|
+
servings: {type: integer, minimum: 1}
|
|
22
|
+
cover_url: {type: string, description: "Public URL of a committed cover image from the media library."}
|
|
23
|
+
uiSchema:
|
|
24
|
+
fields:
|
|
25
|
+
summary: {widget: textarea}
|
|
26
|
+
ingredients: {widget: textarea}
|
|
27
|
+
steps: {widget: textarea}
|
|
28
|
+
---
|
|
29
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
30
|
+
kind: Schema
|
|
31
|
+
metadata: {name: recipe_translations}
|
|
32
|
+
spec:
|
|
33
|
+
title: Recipe translations
|
|
34
|
+
lifecycle: publishing
|
|
35
|
+
localized: true
|
|
36
|
+
# Shares `slug` with its parent; reads merge the parent's total_minutes, servings and cover_url.
|
|
37
|
+
# A translation publishes only while its parent is published.
|
|
38
|
+
translates: {parent: recipes, on: slug}
|
|
39
|
+
uniqueIndexes: [[slug, locale]]
|
|
40
|
+
schema:
|
|
41
|
+
type: object
|
|
42
|
+
required: [slug, locale, title, summary, ingredients, steps]
|
|
43
|
+
properties:
|
|
44
|
+
slug: {type: string, maxLength: 80}
|
|
45
|
+
locale: {type: string, description: "BCP 47 tag, e.g. en"}
|
|
46
|
+
title: {type: string}
|
|
47
|
+
summary: {type: string}
|
|
48
|
+
ingredients: {type: string}
|
|
49
|
+
steps: {type: string}
|
|
50
|
+
uiSchema:
|
|
51
|
+
fields:
|
|
52
|
+
summary: {widget: textarea}
|
|
53
|
+
ingredients: {widget: textarea}
|
|
54
|
+
steps: {widget: textarea}
|
|
55
|
+
---
|
|
56
|
+
# Visitor list for the default locale. Public Views return published rows only.
|
|
57
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
58
|
+
kind: View
|
|
59
|
+
metadata: {name: published_recipes}
|
|
60
|
+
spec:
|
|
61
|
+
surface: public
|
|
62
|
+
sql: SELECT id, slug, title, summary FROM recipes ORDER BY id
|
|
63
|
+
---
|
|
64
|
+
# Visitor list for one translated locale; a translation is hidden while its parent is unpublished.
|
|
65
|
+
apiVersion: cms.mantle.aotter.net/v2
|
|
66
|
+
kind: View
|
|
67
|
+
metadata: {name: published_translations}
|
|
68
|
+
spec:
|
|
69
|
+
surface: public
|
|
70
|
+
input:
|
|
71
|
+
type: object
|
|
72
|
+
required: [locale]
|
|
73
|
+
additionalProperties: false
|
|
74
|
+
properties: {locale: {type: string, minLength: 2, maxLength: 35}}
|
|
75
|
+
sql: SELECT id, slug, title, summary FROM recipe_translations WHERE locale = input.locale ORDER BY id
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Recipes acceptance: drafts stay hidden, publishing via the Admin API makes both locales visible with SEO, JSON-LD,
|
|
2
|
+
// hreflang, Markdown alternates, llms.txt, sitemap and robots.
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { smoke } from "./smoke-kit.mjs";
|
|
5
|
+
|
|
6
|
+
const recipe = {
|
|
7
|
+
slug: "scallion-pancake", title: "蔥油餅", summary: "外酥內軟的蔥油餅 </script>", total_minutes: 25, servings: 2,
|
|
8
|
+
ingredients: "中筋麵粉 200 克\n熱水 120 毫升\n青蔥 3 根", steps: "揉麵團並靜置\n擀開撒蔥捲起\n小火煎至金黃",
|
|
9
|
+
};
|
|
10
|
+
const english = {
|
|
11
|
+
slug: "scallion-pancake", locale: "en", title: "Scallion Pancake", summary: "Crisp outside, soft inside.",
|
|
12
|
+
ingredients: "200 g all-purpose flour\n120 ml hot water\n3 scallions", steps: "Knead and rest the dough\nRoll, add scallions, coil\nPan-fry until golden",
|
|
13
|
+
};
|
|
14
|
+
const jsonLd = html => JSON.parse(/<script type="application\/ld\+json">(.*?)<\/script>/s.exec(html)[1]);
|
|
15
|
+
|
|
16
|
+
await smoke(async ({ origin, owner, call, signIn, step }) => {
|
|
17
|
+
const admin = await signIn(owner);
|
|
18
|
+
const create = async (collection, data) => {
|
|
19
|
+
const created = await call("POST", "/admin/api/entries", { cookie: admin, body: { collection, data } });
|
|
20
|
+
assert.equal(created.status, 200, JSON.stringify(created.body));
|
|
21
|
+
return created.body.entry;
|
|
22
|
+
};
|
|
23
|
+
const publish = (collection, entry) => call("POST", `/admin/api/entries/${entry.id}/publish?collection=${collection}`, { cookie: admin, body: { expectedVersion: entry.version } });
|
|
24
|
+
const parent = await create("recipes", recipe), translation = await create("recipe_translations", english);
|
|
25
|
+
step("admin creates a zh-TW recipe and its en translation as drafts");
|
|
26
|
+
|
|
27
|
+
for (const path of ["/zh-TW/recipes/scallion-pancake", "/zh-TW/recipes/scallion-pancake.md", "/en/recipes/scallion-pancake"]) {
|
|
28
|
+
assert.equal((await call("GET", path)).status, 404, path);
|
|
29
|
+
}
|
|
30
|
+
// Visitor pages read as anonymous even when the request carries an admin session.
|
|
31
|
+
assert.equal((await call("GET", "/zh-TW/recipes/scallion-pancake", { cookie: admin })).status, 404);
|
|
32
|
+
assert.ok(!(await call("GET", "/zh-TW/")).body.includes("scallion-pancake"));
|
|
33
|
+
assert.ok(!(await call("GET", "/llms.txt")).body.includes("scallion-pancake"));
|
|
34
|
+
assert.ok(!(await call("GET", "/sitemap.xml")).body.includes("scallion-pancake"));
|
|
35
|
+
step("drafts are invisible to visitors: detail, Markdown, list, llms.txt and sitemap");
|
|
36
|
+
|
|
37
|
+
assert.equal((await publish("recipe_translations", translation)).status, 409);
|
|
38
|
+
const published = await publish("recipes", parent);
|
|
39
|
+
assert.equal(published.status, 200, JSON.stringify(published.body));
|
|
40
|
+
assert.equal((await call("GET", "/en/recipes/scallion-pancake")).status, 404);
|
|
41
|
+
assert.equal((await publish("recipe_translations", translation)).status, 200);
|
|
42
|
+
step("a translation cannot publish before its parent; publishing both via the Admin API");
|
|
43
|
+
|
|
44
|
+
const zh = await call("GET", "/zh-TW/recipes/scallion-pancake");
|
|
45
|
+
assert.equal(zh.status, 200);
|
|
46
|
+
assert.match(zh.headers.get("content-type"), /text\/html/);
|
|
47
|
+
const html = zh.body;
|
|
48
|
+
assert.match(html, /<html lang="zh-TW">/);
|
|
49
|
+
assert.match(html, /<title>蔥油餅 \| 食譜<\/title>/);
|
|
50
|
+
assert.match(html, /<meta name="description" content="外酥內軟的蔥油餅 <\/script>">/);
|
|
51
|
+
assert.ok(html.includes(`<link rel="canonical" href="${origin}/zh-TW/recipes/scallion-pancake">`));
|
|
52
|
+
assert.ok(html.includes(`<link rel="alternate" type="text/markdown" href="${origin}/zh-TW/recipes/scallion-pancake.md">`));
|
|
53
|
+
step("zh-TW detail has title, description and canonical");
|
|
54
|
+
|
|
55
|
+
for (const [lang, path] of [["zh-TW", "/zh-TW"], ["en", "/en"], ["x-default", "/zh-TW"]]) {
|
|
56
|
+
assert.ok(html.includes(`<link rel="alternate" hreflang="${lang}" href="${origin}${path}/recipes/scallion-pancake">`), lang);
|
|
57
|
+
}
|
|
58
|
+
assert.ok(html.includes(`<a href="/en/recipes/scallion-pancake" hreflang="en" lang="en">English</a>`));
|
|
59
|
+
const en = await call("GET", "/en/recipes/scallion-pancake");
|
|
60
|
+
assert.equal(en.status, 200);
|
|
61
|
+
assert.match(en.body, /<html lang="en">/);
|
|
62
|
+
assert.ok(en.body.includes(`<link rel="canonical" href="${origin}/en/recipes/scallion-pancake">`));
|
|
63
|
+
assert.ok(en.body.includes(`<link rel="alternate" hreflang="zh-TW" href="${origin}/zh-TW/recipes/scallion-pancake">`));
|
|
64
|
+
assert.ok(en.body.includes(`<a href="/zh-TW/recipes/scallion-pancake" hreflang="zh-TW" lang="zh-TW">繁體中文</a>`));
|
|
65
|
+
step("hreflang alternates and the locale switcher link zh-TW and en");
|
|
66
|
+
|
|
67
|
+
const zhData = jsonLd(html), enData = jsonLd(en.body);
|
|
68
|
+
assert.equal(zhData["@type"], "Recipe");
|
|
69
|
+
assert.equal(zhData.name, "蔥油餅");
|
|
70
|
+
assert.equal(zhData.description, recipe.summary);
|
|
71
|
+
assert.deepEqual(zhData.recipeIngredient, ["中筋麵粉 200 克", "熱水 120 毫升", "青蔥 3 根"]);
|
|
72
|
+
assert.equal(enData.name, "Scallion Pancake");
|
|
73
|
+
assert.equal(enData.inLanguage, "en");
|
|
74
|
+
assert.equal(enData.totalTime, "PT25M"); // merged from the parent row
|
|
75
|
+
assert.match(zhData.datePublished, /^\d{4}-/); assert.match(zhData.dateModified, /^\d{4}-/);
|
|
76
|
+
assert.ok(en.body.includes(`<meta property="og:url" content="${origin}/en/recipes/scallion-pancake">`));
|
|
77
|
+
assert.ok(en.body.includes(`<meta property="og:type" content="article">`));
|
|
78
|
+
assert.ok(en.body.includes(`<meta property="og:locale" content="en">`) && en.body.includes(`<meta property="og:locale:alternate" content="zh_TW">`));
|
|
79
|
+
assert.match(en.body, /<section id="quick-answer"><h2>How long does it take\?<\/h2><p>About 25 minutes in total; serves 2.<\/p><\/section>/);
|
|
80
|
+
step("JSON-LD Recipe parses with name and dates; Open Graph url/type/locale; the short answer block is present");
|
|
81
|
+
|
|
82
|
+
const md = await call("GET", "/en/recipes/scallion-pancake.md");
|
|
83
|
+
assert.equal(md.status, 200);
|
|
84
|
+
assert.match(md.headers.get("content-type"), /^text\/markdown/);
|
|
85
|
+
assert.ok(md.body.startsWith("# Scallion Pancake\n"));
|
|
86
|
+
assert.ok(md.body.includes("- 200 g all-purpose flour") && md.body.includes("1. Knead and rest the dough"));
|
|
87
|
+
step(".md alternate returns Markdown");
|
|
88
|
+
|
|
89
|
+
const llms = await call("GET", "/llms.txt");
|
|
90
|
+
assert.ok(llms.body.includes(`- [蔥油餅](${origin}/zh-TW/recipes/scallion-pancake.md)`));
|
|
91
|
+
assert.ok(llms.body.includes(`- [Scallion Pancake](${origin}/en/recipes/scallion-pancake.md)`));
|
|
92
|
+
assert.ok((await call("GET", "/en/")).body.includes(`<a href="/en/recipes/scallion-pancake">Scallion Pancake</a>`));
|
|
93
|
+
const home = await fetch(`${origin}/`, { redirect: "manual" });
|
|
94
|
+
assert.equal(home.status, 302);
|
|
95
|
+
assert.equal(home.headers.get("location"), `${origin}/zh-TW/`);
|
|
96
|
+
const sitemap = await call("GET", "/sitemap.xml");
|
|
97
|
+
assert.match(sitemap.headers.get("content-type"), /^application\/xml/);
|
|
98
|
+
for (const path of ["/zh-TW/", "/en/", "/zh-TW/recipes/scallion-pancake", "/en/recipes/scallion-pancake"]) assert.ok(sitemap.body.includes(`<loc>${origin}${path}</loc>`), path);
|
|
99
|
+
const robots = await call("GET", "/robots.txt");
|
|
100
|
+
assert.ok(robots.body.includes("Disallow: /admin/") && robots.body.includes(`Sitemap: ${origin}/sitemap.xml`));
|
|
101
|
+
step("llms.txt, sitemap (both locales), robots and the locale lists show the published recipe; / redirects to the default locale");
|
|
102
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { r2MediaStorage, type S3Signer } from "@aotter/mantle/media/r2";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cover-image storage on the MEDIA R2 binding (docs/handbook/cloudflare/media-r2.md). Pass the result as Admin's
|
|
5
|
+
* `media` option (with `site` from generated/site.ts); the Admin media library then creates an upload, the browser
|
|
6
|
+
* PUTs the bytes straight to R2 with a presigned URL, and commit copies them under `committed/`.
|
|
7
|
+
* Copy the committed asset's public URL into a recipe's `cover_url`.
|
|
8
|
+
* AGENT: supply a SigV4 signer that returns query-signed PUT URLs, e.g. `new AwsClient({ accessKeyId, secretAccessKey })`
|
|
9
|
+
* from aws4fetch with R2 S3 credentials kept as secrets. Configure bucket CORS for PUT from your origin.
|
|
10
|
+
*/
|
|
11
|
+
export function coverStorage(env: Env, signer: S3Signer) {
|
|
12
|
+
return r2MediaStorage({ bucket: env.MEDIA, signer, endpoint: env.MEDIA_S3_ENDPOINT, publicBase: env.MEDIA_PUBLIC_BASE });
|
|
13
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/** Dependency-free visitor HTML, Markdown and llms.txt for recipes. */
|
|
2
|
+
export const LOCALES = ["zh-TW", "en"] as const;
|
|
3
|
+
export type Locale = (typeof LOCALES)[number];
|
|
4
|
+
export const DEFAULT_LOCALE: Locale = "zh-TW";
|
|
5
|
+
|
|
6
|
+
export interface Recipe {
|
|
7
|
+
slug: string; locale: Locale; title: string; summary: string; ingredients: string; steps: string;
|
|
8
|
+
total_minutes: number; servings: number; cover_url?: string | null; published_at?: string | null; updated_at?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface Listed { slug: string; title: string; summary: string }
|
|
11
|
+
|
|
12
|
+
// AGENT: move UI strings to your translation workflow when you add locales.
|
|
13
|
+
const text = {
|
|
14
|
+
"zh-TW": { site: "食譜", description: "家常食譜,中英對照。", how: "需要多久?", answer: (m: number, s: number) => `總共約 ${m} 分鐘,可供 ${s} 人份。`, ingredients: "材料", steps: "步驟", all: "所有食譜", name: "繁體中文" },
|
|
15
|
+
en: { site: "Recipes", description: "Home recipes in Traditional Chinese and English.", how: "How long does it take?", answer: (m: number, s: number) => `About ${m} minutes in total; serves ${s}.`, ingredients: "Ingredients", steps: "Steps", all: "All recipes", name: "English" },
|
|
16
|
+
} satisfies Record<Locale, unknown>;
|
|
17
|
+
|
|
18
|
+
const escape = (value: unknown) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]!));
|
|
19
|
+
const lines = (value: string) => value.split("\n").map(line => line.trim()).filter(Boolean);
|
|
20
|
+
const recipePath = (locale: Locale, slug: string) => `/${locale}/recipes/${slug}`;
|
|
21
|
+
|
|
22
|
+
/** hreflang alternates for every locale the page exists in, plus x-default (the default locale). */
|
|
23
|
+
function alternates(origin: string, locales: readonly Locale[], path: (locale: Locale) => string) {
|
|
24
|
+
return locales.map(l => `<link rel="alternate" hreflang="${l}" href="${origin}${path(l)}">`).join("")
|
|
25
|
+
+ `<link rel="alternate" hreflang="x-default" href="${origin}${path(DEFAULT_LOCALE)}">`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Open Graph: og:locale uses underscores (zh_TW); the other locales the page exists in are alternates. */
|
|
29
|
+
function openGraph(type: "website" | "article", url: string, locale: Locale, locales: readonly Locale[], title: string, description: string) {
|
|
30
|
+
const og = (property: string, content: string) => `<meta property="og:${property}" content="${escape(content)}">`;
|
|
31
|
+
return og("type", type) + og("url", url) + og("site_name", text[locale].site) + og("title", title) + og("description", description)
|
|
32
|
+
+ og("locale", locale.replace("-", "_")) + locales.filter(l => l !== locale).map(l => og("locale:alternate", l.replace("-", "_"))).join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function documentHtml(locale: Locale, head: string, body: string) {
|
|
36
|
+
// AGENT: real layout and styling (stylesheet, header, footer, images sizes); keep the head tags.
|
|
37
|
+
return `<!doctype html><html lang="${locale}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">${head}</head><body>${body}</body></html>`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function switcher(locales: readonly Locale[], path: (locale: Locale) => string, current: Locale) {
|
|
41
|
+
return `<nav aria-label="Language">${locales.map(l => l === current ? `<strong>${text[l].name}</strong>` : `<a href="${path(l)}" hreflang="${l}" lang="${l}">${text[l].name}</a>`).join(" | ")}</nav>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function listHtml(origin: string, locale: Locale, rows: readonly Listed[]) {
|
|
45
|
+
const t = text[locale], path = (l: Locale) => `/${l}/`;
|
|
46
|
+
const head = `<title>${escape(t.site)}</title><meta name="description" content="${escape(t.description)}">`
|
|
47
|
+
+ `<link rel="canonical" href="${origin}${path(locale)}">${alternates(origin, LOCALES, path)}`
|
|
48
|
+
+ openGraph("website", `${origin}${path(locale)}`, locale, LOCALES, t.site, t.description);
|
|
49
|
+
const items = rows.map(r => `<li><a href="${recipePath(locale, r.slug)}">${escape(r.title)}</a><p>${escape(r.summary)}</p></li>`).join("");
|
|
50
|
+
return documentHtml(locale, head, `${switcher(LOCALES, path, locale)}<h1>${escape(t.site)}</h1><ul>${items}</ul>`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function recipeHtml(origin: string, locale: Locale, r: Recipe, locales: readonly Locale[]) {
|
|
54
|
+
const t = text[locale], path = (l: Locale) => recipePath(l, r.slug), canonical = `${origin}${path(locale)}`;
|
|
55
|
+
const jsonLd = {
|
|
56
|
+
"@context": "https://schema.org", "@type": "Recipe", name: r.title, description: r.summary, inLanguage: locale, url: canonical,
|
|
57
|
+
...(r.cover_url ? { image: [r.cover_url] } : {}), ...(r.published_at ? { datePublished: r.published_at } : {}), ...(r.updated_at ? { dateModified: r.updated_at } : {}),
|
|
58
|
+
totalTime: `PT${r.total_minutes}M`, recipeYield: String(r.servings),
|
|
59
|
+
recipeIngredient: lines(r.ingredients),
|
|
60
|
+
recipeInstructions: lines(r.steps).map(step => ({ "@type": "HowToStep", text: step })),
|
|
61
|
+
};
|
|
62
|
+
const head = `<title>${escape(r.title)} | ${escape(t.site)}</title><meta name="description" content="${escape(r.summary)}">`
|
|
63
|
+
+ `<link rel="canonical" href="${canonical}">${alternates(origin, locales, path)}`
|
|
64
|
+
+ `<link rel="alternate" type="text/markdown" href="${canonical}.md">`
|
|
65
|
+
+ openGraph("article", canonical, locale, locales, r.title, r.summary)
|
|
66
|
+
+ (r.cover_url ? `<meta property="og:image" content="${escape(r.cover_url)}">` : "")
|
|
67
|
+
// `<` is escaped so admin-authored text cannot close the script element.
|
|
68
|
+
+ `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`;
|
|
69
|
+
const body = `${switcher(locales, path, locale)}<p><a href="/${locale}/">${escape(t.all)}</a></p>`
|
|
70
|
+
+ `<article><h1>${escape(r.title)}</h1><p>${escape(r.summary)}</p>`
|
|
71
|
+
+ (r.cover_url ? `<img src="${escape(r.cover_url)}" alt="${escape(r.title)}">` : "")
|
|
72
|
+
// Short answer block: one question and a one-sentence answer that search and answer engines can quote.
|
|
73
|
+
+ `<section id="quick-answer"><h2>${escape(t.how)}</h2><p>${escape(t.answer(r.total_minutes, r.servings))}</p></section>`
|
|
74
|
+
+ `<h2>${escape(t.ingredients)}</h2><ul>${lines(r.ingredients).map(i => `<li>${escape(i)}</li>`).join("")}</ul>`
|
|
75
|
+
+ `<h2>${escape(t.steps)}</h2><ol>${lines(r.steps).map(s => `<li>${escape(s)}</li>`).join("")}</ol></article>`;
|
|
76
|
+
return documentHtml(locale, head, body);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function recipeMarkdown(r: Recipe) {
|
|
80
|
+
const t = text[r.locale];
|
|
81
|
+
return [`# ${r.title}`, "", r.summary, "", `## ${t.how}`, "", t.answer(r.total_minutes, r.servings), "",
|
|
82
|
+
`## ${t.ingredients}`, "", ...lines(r.ingredients).map(i => `- ${i}`), "",
|
|
83
|
+
`## ${t.steps}`, "", ...lines(r.steps).map((s, i) => `${i + 1}. ${s}`), ""].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** llms.txt (https://llmstxt.org): one section per locale, each recipe linking to its Markdown alternate. */
|
|
87
|
+
export function llmsTxt(origin: string, lists: readonly (readonly [Locale, readonly Listed[]])[]) {
|
|
88
|
+
return [`# ${text.en.site}`, "", `> ${text.en.description}`, "",
|
|
89
|
+
...lists.flatMap(([locale, rows]) => [`## ${text[locale].name} (${locale})`, "",
|
|
90
|
+
...rows.map(r => `- [${r.title}](${origin}${recipePath(locale, r.slug)}.md): ${r.summary.replace(/\s+/g, " ")}`), ""])].join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** sitemap.xml: each locale's list page and every published recipe in it (only published rows reach `lists`). */
|
|
94
|
+
export function sitemapXml(origin: string, lists: readonly (readonly [Locale, readonly Listed[]])[]) {
|
|
95
|
+
const paths = lists.flatMap(([locale, rows]) => [`/${locale}/`, ...rows.map(r => recipePath(locale, r.slug))]);
|
|
96
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${paths.map(path => `<url><loc>${escape(origin + path)}</loc></url>`).join("")}</urlset>\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const robotsTxt = (origin: string) => `User-agent: *\nDisallow: /admin/\nSitemap: ${origin}/sitemap.xml\n`;
|