@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.
Files changed (495) hide show
  1. package/README.md +166 -298
  2. package/dist/auth/adapter.d.ts +32 -0
  3. package/dist/auth/adapter.js +288 -0
  4. package/dist/auth/admin.d.ts +3 -0
  5. package/dist/auth/admin.js +21 -0
  6. package/dist/auth/caller.d.ts +86 -0
  7. package/dist/auth/caller.js +155 -0
  8. package/dist/auth/hardening.d.ts +24 -0
  9. package/dist/auth/hardening.js +40 -0
  10. package/dist/auth/identity.d.ts +121 -0
  11. package/dist/auth/identity.js +203 -0
  12. package/dist/auth/oauth.d.ts +63 -0
  13. package/dist/auth/oauth.js +217 -0
  14. package/dist/auth/resource-seeds.js +10 -0
  15. package/dist/auth/routes.d.ts +32 -0
  16. package/dist/auth/routes.js +226 -0
  17. package/dist/build/compiler.d.ts +721 -0
  18. package/dist/build/compiler.js +7029 -0
  19. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +439 -0
  20. package/dist/build/spec/domain/model/RuntimePlan.d.ts +168 -0
  21. package/dist/build/spec/domain/model/SqlIr.d.ts +129 -0
  22. package/dist/build/spec/domain/model/UserFields.d.ts +21 -0
  23. package/dist/build/spec/domain/service/PlanFingerprint.d.ts +7 -0
  24. package/dist/build/spec/kernel/diagnostic.d.ts +108 -0
  25. package/dist/bun/assets.d.ts +2 -0
  26. package/dist/bun/assets.js +24 -0
  27. package/dist/domain/adminOrder.js +24 -0
  28. package/dist/domain/columns.js +57 -0
  29. package/dist/domain/describe/chunk-ZVXBGP7Y.js +424 -0
  30. package/dist/domain/describe/index.d.ts +58 -0
  31. package/dist/domain/describe/index.js +115 -0
  32. package/dist/domain/describe/target-hash.d.ts +8 -0
  33. package/dist/domain/describe/target-hash.js +12 -0
  34. package/dist/domain/describe/targets.d.ts +40 -0
  35. package/dist/domain/entry.js +33 -0
  36. package/dist/domain/generated-tools.js +19 -0
  37. package/dist/domain/generation.d.ts +16 -0
  38. package/dist/domain/generation.js +1 -0
  39. package/dist/domain/input.js +17 -0
  40. package/dist/domain/model.js +1 -0
  41. package/dist/domain/paging.js +230 -0
  42. package/dist/domain/recipes.js +24 -0
  43. package/dist/domain/userReferences.js +5 -0
  44. package/dist/domain/wireScalars.js +47 -0
  45. package/dist/infrastructure/adminEntries.js +153 -0
  46. package/dist/infrastructure/adminPaging.js +72 -0
  47. package/dist/infrastructure/artifacts.js +71 -0
  48. package/dist/infrastructure/authSchema.js +83 -0
  49. package/dist/infrastructure/bundle.js +188 -0
  50. package/dist/infrastructure/cli.js +118 -0
  51. package/dist/infrastructure/generate.d.ts +12 -0
  52. package/dist/infrastructure/generate.js +189 -0
  53. package/dist/infrastructure/generateAuth.d.ts +8 -0
  54. package/dist/infrastructure/generateAuth.js +74 -0
  55. package/dist/infrastructure/help.js +60 -0
  56. package/dist/infrastructure/init.js +54 -0
  57. package/dist/infrastructure/inputs.js +41 -0
  58. package/dist/infrastructure/mounts.js +25 -0
  59. package/dist/infrastructure/wrangler.js +109 -0
  60. package/dist/kernel/authorization.d.ts +4 -0
  61. package/dist/kernel/authorization.js +16 -0
  62. package/dist/kernel/binds.d.ts +60 -0
  63. package/dist/kernel/binds.js +49 -0
  64. package/dist/kernel/caller-identity.d.ts +24 -0
  65. package/dist/kernel/caller-identity.js +22 -0
  66. package/dist/kernel/contract-error.d.ts +12 -0
  67. package/dist/kernel/contract-error.js +14 -0
  68. package/dist/kernel/contracts.d.ts +143 -0
  69. package/dist/kernel/contracts.js +97 -0
  70. package/dist/kernel/cron.d.ts +1 -0
  71. package/dist/kernel/cron.js +58 -0
  72. package/dist/kernel/dialect/common.d.ts +38 -0
  73. package/dist/kernel/dialect/common.js +6 -0
  74. package/dist/kernel/dialect/postgres.d.ts +16 -0
  75. package/dist/kernel/dialect/postgres.js +18 -0
  76. package/dist/kernel/dialect/sqlite.d.ts +20 -0
  77. package/dist/kernel/dialect/sqlite.js +34 -0
  78. package/dist/kernel/drain.d.ts +5 -0
  79. package/dist/kernel/drain.js +35 -0
  80. package/dist/kernel/driver/bun-sqlite.d.ts +19 -0
  81. package/dist/kernel/driver/bun-sqlite.js +77 -0
  82. package/dist/kernel/driver/d1.d.ts +24 -0
  83. package/dist/kernel/driver/d1.js +22 -0
  84. package/dist/kernel/driver/postgres.d.ts +25 -0
  85. package/dist/kernel/driver/postgres.js +99 -0
  86. package/dist/kernel/driver/transaction-context.d.ts +10 -0
  87. package/dist/kernel/driver/transaction-context.js +29 -0
  88. package/dist/kernel/email.d.ts +28 -0
  89. package/dist/kernel/email.js +38 -0
  90. package/dist/kernel/host-access.d.ts +10 -0
  91. package/dist/kernel/host-access.js +65 -0
  92. package/dist/kernel/host-caller.d.ts +39 -0
  93. package/dist/kernel/host-caller.js +56 -0
  94. package/dist/kernel/json-body.d.ts +4 -0
  95. package/dist/kernel/json-body.js +44 -0
  96. package/dist/kernel/mcp-apps.d.ts +69 -0
  97. package/dist/kernel/mcp-apps.js +40 -0
  98. package/dist/kernel/mcp-call.d.ts +5 -0
  99. package/dist/kernel/mcp-call.js +25 -0
  100. package/dist/kernel/mcp-handler.js +371 -0
  101. package/dist/kernel/mcp.d.ts +110 -0
  102. package/dist/kernel/mcp.js +50 -0
  103. package/dist/kernel/media.d.ts +27 -0
  104. package/dist/kernel/media.js +216 -0
  105. package/dist/kernel/paging.d.ts +55 -0
  106. package/dist/kernel/paging.js +95 -0
  107. package/dist/kernel/publishing.d.ts +4 -0
  108. package/dist/kernel/publishing.js +16 -0
  109. package/dist/kernel/r2-media.d.ts +42 -0
  110. package/dist/kernel/r2-media.js +78 -0
  111. package/dist/kernel/rest-dispatch.js +38 -0
  112. package/dist/kernel/rest-router.js +64 -0
  113. package/dist/kernel/rest.d.ts +58 -0
  114. package/dist/kernel/rest.js +41 -0
  115. package/dist/kernel/scalars.d.ts +6 -0
  116. package/dist/kernel/scalars.js +44 -0
  117. package/dist/kernel/search.js +5 -0
  118. package/dist/kernel/site-types.d.ts +146 -0
  119. package/dist/kernel/site-types.js +1 -0
  120. package/dist/kernel/site.d.ts +39 -0
  121. package/dist/kernel/site.js +32 -0
  122. package/dist/kernel/store.d.ts +175 -0
  123. package/dist/kernel/store.js +269 -0
  124. package/dist/kernel/surface.d.ts +10 -0
  125. package/dist/kernel/surface.js +19 -0
  126. package/dist/kernel/sweep.d.ts +17 -0
  127. package/dist/kernel/sweep.js +27 -0
  128. package/dist/kernel/translations.d.ts +1 -0
  129. package/dist/kernel/translations.js +13 -0
  130. package/dist/kernel/user-caller.d.ts +31 -0
  131. package/dist/kernel/user-caller.js +67 -0
  132. package/dist/kernel/user-field-name.js +3 -0
  133. package/dist/kernel/user-fields.d.ts +35 -0
  134. package/dist/kernel/user-fields.js +81 -0
  135. package/dist/kernel/validation.d.ts +15 -0
  136. package/dist/kernel/validation.js +54 -0
  137. package/dist/kernel/web-gateway.js +68 -0
  138. package/dist/kernel/web-head.js +15 -0
  139. package/dist/kernel/web-index.js +40 -0
  140. package/dist/kernel/web-response.js +8 -0
  141. package/dist/kernel/web.d.ts +77 -0
  142. package/dist/kernel/web.js +124 -0
  143. package/dist/kernel/where.js +27 -0
  144. package/dist/testing/index.d.ts +1 -0
  145. package/dist/testing/index.js +1 -0
  146. package/dist/usecase/adminBindings.js +143 -0
  147. package/dist/usecase/adminClient.js +87 -0
  148. package/dist/usecase/adminDescriptors.js +107 -0
  149. package/dist/usecase/generate.js +166 -0
  150. package/dist/usecase/handlers.js +21 -0
  151. package/dist/usecase/mcp-media.js +17 -0
  152. package/dist/usecase/mcp.js +100 -0
  153. package/dist/usecase/paging.js +71 -0
  154. package/dist/usecase/publishing.js +63 -0
  155. package/dist/usecase/refusals.js +27 -0
  156. package/dist/usecase/rest.js +54 -0
  157. package/dist/usecase/search.js +7 -0
  158. package/dist/usecase/site.js +39 -0
  159. package/dist/usecase/stores.js +64 -0
  160. package/dist/usecase/tool-version.js +9 -0
  161. package/dist/usecase/translations.js +78 -0
  162. package/dist/usecase/ttl.js +20 -0
  163. package/dist/usecase/validation.js +241 -0
  164. package/dist/usecase/web.js +23 -0
  165. package/docs/examples/README.md +39 -18
  166. package/docs/examples/app-shell/.dev.vars.example +4 -0
  167. package/docs/examples/app-shell/README.md +121 -0
  168. package/docs/examples/app-shell/accept.mjs +91 -0
  169. package/docs/examples/app-shell/gitignore +7 -0
  170. package/docs/examples/app-shell/package.json +43 -0
  171. package/docs/examples/app-shell/pnpm-workspace.yaml +8 -0
  172. package/docs/examples/app-shell/smoke-kit.mjs +168 -0
  173. package/docs/examples/app-shell/src/auth.ts +58 -0
  174. package/docs/examples/app-shell/src/handlers.ts +5 -0
  175. package/docs/examples/app-shell/src/index.ts +4 -0
  176. package/docs/examples/app-shell/src/routes.ts +14 -0
  177. package/docs/examples/app-shell/src/schedule.ts +8 -0
  178. package/docs/examples/app-shell/src/service.ts +70 -0
  179. package/docs/examples/app-shell/src/sign-in.ts +41 -0
  180. package/docs/examples/app-shell/src/signup.ts +8 -0
  181. package/docs/examples/app-shell/tsconfig.json +26 -0
  182. package/docs/examples/app-shell/worker-configuration.d.ts +22 -0
  183. package/docs/examples/app-shell/wrangler.jsonc +62 -0
  184. package/docs/examples/club-events/README.md +65 -0
  185. package/docs/examples/club-events/manifests/club-events.yaml +247 -0
  186. package/docs/examples/club-events/smoke.mjs +127 -0
  187. package/docs/examples/club-events/src/handlers.ts +25 -0
  188. package/docs/examples/club-events/src/signup.ts +9 -0
  189. package/docs/examples/generated-web/README.md +78 -0
  190. package/docs/examples/generated-web/posts.yaml +38 -0
  191. package/docs/examples/generated-web/seed.sql +6 -0
  192. package/docs/examples/generated-web/src/worker.ts +43 -0
  193. package/docs/examples/generated-web/tsconfig.json +8 -0
  194. package/docs/examples/generated-web/wrangler.jsonc +8 -0
  195. package/docs/examples/purchasing/.dev.vars.example +6 -0
  196. package/docs/examples/purchasing/README.md +99 -0
  197. package/docs/examples/purchasing/manifests/purchasing.yaml +374 -0
  198. package/docs/examples/purchasing/seed.sql +4 -0
  199. package/docs/examples/purchasing/smoke.mjs +191 -0
  200. package/docs/examples/purchasing/src/admin.tsx +13 -0
  201. package/docs/examples/purchasing/src/handlers.ts +33 -0
  202. package/docs/examples/purchasing/src/notify.ts +29 -0
  203. package/docs/examples/purchasing/src/requests-page.ts +57 -0
  204. package/docs/examples/purchasing/src/routes.ts +53 -0
  205. package/docs/examples/purchasing/src/schedule.ts +31 -0
  206. package/docs/examples/purchasing/src/signup.ts +5 -0
  207. package/docs/examples/purchasing/src/supplier.ts +32 -0
  208. package/docs/examples/purchasing/wrangler.jsonc +69 -0
  209. package/docs/examples/recipes/README.md +60 -0
  210. package/docs/examples/recipes/manifests/recipes.yaml +75 -0
  211. package/docs/examples/recipes/smoke.mjs +102 -0
  212. package/docs/examples/recipes/src/media.ts +13 -0
  213. package/docs/examples/recipes/src/pages.ts +99 -0
  214. package/docs/examples/recipes/src/routes.ts +71 -0
  215. package/docs/examples/recipes/src/site-config.ts +12 -0
  216. package/docs/examples/recipes/wrangler.jsonc +74 -0
  217. package/docs/handbook/cloudflare/authentication.md +180 -322
  218. package/docs/handbook/cloudflare/chatgpt-sites.md +5 -23
  219. package/docs/handbook/cloudflare/deploy-and-operate.md +55 -77
  220. package/docs/handbook/cloudflare/media-r2.md +61 -205
  221. package/docs/handbook/cloudflare/service-entry.md +239 -0
  222. package/docs/handbook/concepts/authorization.md +10 -107
  223. package/docs/handbook/concepts/four-atoms.md +9 -168
  224. package/docs/handbook/concepts/lifecycle-and-locales.md +9 -132
  225. package/docs/handbook/concepts/mcp-and-agents.md +24 -121
  226. package/docs/handbook/concepts/procedures-and-triggers.md +5 -217
  227. package/docs/handbook/concepts/runtime-and-adapters.md +15 -169
  228. package/docs/handbook/concepts/views.md +10 -185
  229. package/docs/handbook/examples/hub.md +3 -8
  230. package/docs/handbook/guides/adapter-guide.md +11 -0
  231. package/docs/handbook/guides/admin-ui.md +115 -133
  232. package/docs/handbook/guides/agent-setup.md +64 -74
  233. package/docs/handbook/guides/agent-workflow-acceptance.md +232 -0
  234. package/docs/handbook/guides/email.md +86 -0
  235. package/docs/handbook/guides/local-testing.md +40 -0
  236. package/docs/handbook/guides/mcp-apps.md +560 -0
  237. package/docs/handbook/guides/native-admin.md +566 -0
  238. package/docs/handbook/guides/native-execution.md +133 -0
  239. package/docs/handbook/guides/oauth.md +219 -0
  240. package/docs/handbook/guides/postgres-adapters.md +56 -0
  241. package/docs/handbook/guides/public-pages.md +87 -0
  242. package/docs/handbook/guides/size-and-startup.md +9 -0
  243. package/docs/handbook/guides/sql-patterns.md +266 -0
  244. package/docs/handbook/guides/typed-queries.md +211 -107
  245. package/docs/handbook/navigation.json +176 -55
  246. package/docs/handbook/reference/admin-descriptors.md +53 -0
  247. package/docs/handbook/reference/authorization.md +39 -266
  248. package/docs/handbook/reference/describe.md +56 -0
  249. package/docs/handbook/reference/diagnostics.md +19 -186
  250. package/docs/handbook/reference/features.md +45 -55
  251. package/docs/handbook/reference/manifest.md +6 -129
  252. package/docs/handbook/reference/procedure.md +11 -303
  253. package/docs/handbook/reference/schema.md +64 -240
  254. package/docs/handbook/reference/site-config.md +5 -218
  255. package/docs/handbook/reference/sql.md +64 -0
  256. package/docs/handbook/reference/surface.md +392 -201
  257. package/docs/handbook/reference/trigger.md +56 -192
  258. package/docs/handbook/reference/view.md +24 -243
  259. package/docs/handbook/releases/index.md +244 -77
  260. package/docs/handbook/start/agents.md +45 -0
  261. package/docs/handbook/start/overview.md +9 -48
  262. package/docs/handbook/start/project-and-cli.md +51 -104
  263. package/docs/handbook/start/quickstart-worker.md +43 -178
  264. package/docs/skills/develop/SKILL.md +79 -249
  265. package/docs/skills/integrate/SKILL.md +39 -50
  266. package/docs/skills/media-gc/SKILL.md +34 -10
  267. package/docs/skills/provision/SKILL.md +36 -186
  268. package/docs/skills/update/SKILL.md +23 -38
  269. package/docs/source/scripts/postgres-adapter.ts +101 -0
  270. package/docs/upgrade-0.1-to-0.2.md +227 -0
  271. package/package.json +120 -131
  272. package/skills/README.md +48 -108
  273. package/skills/mantle/SKILL.md +76 -0
  274. package/dist/admin-ui.d.ts +0 -2
  275. package/dist/admin-ui.d.ts.map +0 -1
  276. package/dist/admin-ui.js +0 -2
  277. package/dist/admin-ui.js.map +0 -1
  278. package/dist/admin.d.ts +0 -2
  279. package/dist/admin.d.ts.map +0 -1
  280. package/dist/admin.js +0 -2
  281. package/dist/admin.js.map +0 -1
  282. package/dist/auth.d.ts +0 -2
  283. package/dist/auth.d.ts.map +0 -1
  284. package/dist/auth.js +0 -2
  285. package/dist/auth.js.map +0 -1
  286. package/dist/bun.d.ts +0 -2
  287. package/dist/bun.d.ts.map +0 -1
  288. package/dist/bun.js +0 -2
  289. package/dist/bun.js.map +0 -1
  290. package/dist/cli/generate-cloudflare.d.ts +0 -7
  291. package/dist/cli/generate-cloudflare.d.ts.map +0 -1
  292. package/dist/cli/generate-cloudflare.js +0 -123
  293. package/dist/cli/generate-cloudflare.js.map +0 -1
  294. package/dist/cli/generate-project.d.ts +0 -25
  295. package/dist/cli/generate-project.d.ts.map +0 -1
  296. package/dist/cli/generate-project.js +0 -282
  297. package/dist/cli/generate-project.js.map +0 -1
  298. package/dist/cli/generate-sites.d.ts +0 -8
  299. package/dist/cli/generate-sites.d.ts.map +0 -1
  300. package/dist/cli/generate-sites.js +0 -273
  301. package/dist/cli/generate-sites.js.map +0 -1
  302. package/dist/cli/generate.d.ts +0 -20
  303. package/dist/cli/generate.d.ts.map +0 -1
  304. package/dist/cli/generate.js +0 -299
  305. package/dist/cli/generate.js.map +0 -1
  306. package/dist/cli/harness.d.ts +0 -3
  307. package/dist/cli/harness.d.ts.map +0 -1
  308. package/dist/cli/harness.js +0 -214
  309. package/dist/cli/harness.js.map +0 -1
  310. package/dist/cli/main.d.ts +0 -3
  311. package/dist/cli/main.d.ts.map +0 -1
  312. package/dist/cli/main.js +0 -77
  313. package/dist/cli/main.js.map +0 -1
  314. package/dist/cli/skills.d.ts +0 -3
  315. package/dist/cli/skills.d.ts.map +0 -1
  316. package/dist/cli/skills.js +0 -128
  317. package/dist/cli/skills.js.map +0 -1
  318. package/dist/cloudflare.d.ts +0 -2
  319. package/dist/cloudflare.d.ts.map +0 -1
  320. package/dist/cloudflare.js +0 -2
  321. package/dist/cloudflare.js.map +0 -1
  322. package/dist/codegen/emitMantleModule.d.ts +0 -22
  323. package/dist/codegen/emitMantleModule.d.ts.map +0 -1
  324. package/dist/codegen/emitMantleModule.js +0 -291
  325. package/dist/codegen/emitMantleModule.js.map +0 -1
  326. package/dist/codegen.d.ts +0 -2
  327. package/dist/codegen.d.ts.map +0 -1
  328. package/dist/codegen.js +0 -2
  329. package/dist/codegen.js.map +0 -1
  330. package/dist/runtime-testing.d.ts +0 -2
  331. package/dist/runtime-testing.d.ts.map +0 -1
  332. package/dist/runtime-testing.js +0 -2
  333. package/dist/runtime-testing.js.map +0 -1
  334. package/dist/runtime.d.ts +0 -2
  335. package/dist/runtime.d.ts.map +0 -1
  336. package/dist/runtime.js +0 -2
  337. package/dist/runtime.js.map +0 -1
  338. package/dist/spec.d.ts +0 -2
  339. package/dist/spec.d.ts.map +0 -1
  340. package/dist/spec.js +0 -2
  341. package/dist/spec.js.map +0 -1
  342. package/dist/vercel-libsql.d.ts +0 -2
  343. package/dist/vercel-libsql.d.ts.map +0 -1
  344. package/dist/vercel-libsql.js +0 -2
  345. package/dist/vercel-libsql.js.map +0 -1
  346. package/dist/vercel.d.ts +0 -2
  347. package/dist/vercel.d.ts.map +0 -1
  348. package/dist/vercel.js +0 -2
  349. package/dist/vercel.js.map +0 -1
  350. package/dist/web.d.ts +0 -2
  351. package/dist/web.d.ts.map +0 -1
  352. package/dist/web.js +0 -2
  353. package/dist/web.js.map +0 -1
  354. package/docs/adapter-guide.md +0 -222
  355. package/docs/adr/0001-four-atom-manifest-model.md +0 -440
  356. package/docs/adr/0002-closed-enums-for-bindings.md +0 -253
  357. package/docs/adr/0007-ai-as-primary-author.md +0 -473
  358. package/docs/adr/0008-structured-diagnostic-shape.md +0 -338
  359. package/docs/adr/0009-consumer-supplied-manifests.md +0 -149
  360. package/docs/adr/0010-locale-and-translates.md +0 -453
  361. package/docs/adr/0011-adapter-port-spec.md +0 -281
  362. package/docs/adr/0012-views-as-public-rest.md +0 -161
  363. package/docs/adr/0013-agent-provisioned-consumer-projects.md +0 -154
  364. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +0 -900
  365. package/docs/adr/0016-site-semantic-layer.md +0 -70
  366. package/docs/adr/0017-media-multi-variant-agent-side-optimization.md +0 -181
  367. package/docs/adr/0018-core-starters-repository-boundary.md +0 -179
  368. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +0 -240
  369. package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +0 -199
  370. package/docs/adr/0021-retire-starter-scaffolding.md +0 -30
  371. package/docs/adr/0022-caller-observed-version-occ.md +0 -212
  372. package/docs/adr/0023-port-failure-contract.md +0 -69
  373. package/docs/adr/0024-manifest-native-schema-tables.md +0 -264
  374. package/docs/adr/0025-reserved-native-columns-and-published-only-public-views.md +0 -105
  375. package/docs/adr/0026-progressive-generate.md +0 -36
  376. package/docs/adr/0027-scheduled-procedure-triggers.md +0 -31
  377. package/docs/adr/0028-schema-ttl-expiry.md +0 -33
  378. package/docs/adr/README.md +0 -81
  379. package/docs/adr/adr-lite-803-request-diagnostics.md +0 -38
  380. package/docs/adr/adr-lite-808-route-readiness.md +0 -47
  381. package/docs/adr/adr-lite-809-bounded-public-content.md +0 -71
  382. package/docs/adr/adr-lite-812-native-parity.md +0 -150
  383. package/docs/adr/adr-lite-823-home-statistics.md +0 -64
  384. package/docs/adr/adr-lite-842-mcp-authoring.md +0 -30
  385. package/docs/adr/adr-lite-845-frontend-client.md +0 -38
  386. package/docs/adr/adr-lite-861-admin-webmcp.md +0 -42
  387. package/docs/adr/adr-lite-909-admin-ui-kit.md +0 -37
  388. package/docs/agent-prompts.md +0 -64
  389. package/docs/api-mcp-authorization.md +0 -5
  390. package/docs/assets/mantle-admin-developer.png +0 -0
  391. package/docs/assets/mantle-admin-operations.png +0 -0
  392. package/docs/assets/mantle-hero.jpg +0 -0
  393. package/docs/auth-hosting-model.md +0 -166
  394. package/docs/cloudflare-low-level-composition.md +0 -5
  395. package/docs/consumer-onboarding-audit.md +0 -65
  396. package/docs/deferred-lifecycle-queues.md +0 -5
  397. package/docs/design-atoms.md +0 -16
  398. package/docs/design-references/editorial-blog-2026-05-05.md +0 -218
  399. package/docs/direct-authoring.md +0 -5
  400. package/docs/examples/builtin-commerce.md +0 -269
  401. package/docs/examples/builtin-intake.md +0 -143
  402. package/docs/examples/builtin-legal-documents.md +0 -189
  403. package/docs/examples/builtin-procurement.md +0 -241
  404. package/docs/examples/builtin-publication.md +0 -241
  405. package/docs/examples/builtin-reservation.md +0 -149
  406. package/docs/examples/cf-primitives-commerce-inventory.md +0 -809
  407. package/docs/examples/cf-primitives-guarded-api.md +0 -429
  408. package/docs/examples/cf-primitives-intake-hooks.md +0 -319
  409. package/docs/examples/host-chatgpt-sites/.openai/hosting.json +0 -1
  410. package/docs/examples/host-chatgpt-sites/README.md +0 -70
  411. package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +0 -8
  412. package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +0 -294
  413. package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +0 -5
  414. package/docs/examples/host-chatgpt-sites/drizzle/0003_store_instance_id.sql +0 -1
  415. package/docs/examples/host-chatgpt-sites/drizzle/0004_mantle.sql +0 -10
  416. package/docs/examples/host-chatgpt-sites/drizzle/0005_mantle.sql +0 -24
  417. package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +0 -1
  418. package/docs/examples/host-chatgpt-sites/drizzle/meta/mantle-state.json +0 -60
  419. package/docs/examples/host-chatgpt-sites/manifests/site.yaml +0 -27
  420. package/docs/examples/host-chatgpt-sites/package.json +0 -1
  421. package/docs/examples/host-chatgpt-sites/public/site.css +0 -1
  422. package/docs/examples/host-chatgpt-sites/scripts/build.mjs +0 -13
  423. package/docs/examples/host-chatgpt-sites/scripts/check.mjs +0 -101
  424. package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +0 -43
  425. package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +0 -66
  426. package/docs/examples/host-chatgpt-sites/src/index.ts +0 -46
  427. package/docs/examples/host-chatgpt-sites/src/mcp.ts +0 -49
  428. package/docs/examples/host-chatgpt-sites/src/media.ts +0 -109
  429. package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +0 -38
  430. package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +0 -1
  431. package/docs/examples/host-chatgpt-sites/src/web.ts +0 -35
  432. package/docs/examples/host-chatgpt-sites/tsconfig.json +0 -1
  433. package/docs/examples/host-chatgpt-sites/wrangler.jsonc +0 -10
  434. package/docs/examples/host-local-admin-otp/.dev.vars.example +0 -3
  435. package/docs/examples/host-local-admin-otp/README.md +0 -80
  436. package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +0 -5
  437. package/docs/examples/host-local-admin-otp/manifests/site.yaml +0 -25
  438. package/docs/examples/host-local-admin-otp/package.json +0 -29
  439. package/docs/examples/host-local-admin-otp/public/.gitkeep +0 -1
  440. package/docs/examples/host-local-admin-otp/smoke.mjs +0 -141
  441. package/docs/examples/host-local-admin-otp/src/index.ts +0 -54
  442. package/docs/examples/host-local-admin-otp/tsconfig.json +0 -17
  443. package/docs/examples/host-local-admin-otp/wrangler.jsonc +0 -23
  444. package/docs/examples/host-minimal-worker/README.md +0 -41
  445. package/docs/examples/host-minimal-worker/manifests/site.yaml +0 -25
  446. package/docs/examples/host-minimal-worker/package.json +0 -26
  447. package/docs/examples/host-minimal-worker/smoke.mjs +0 -23
  448. package/docs/examples/host-minimal-worker/src/index.ts +0 -4
  449. package/docs/examples/host-minimal-worker/tsconfig.json +0 -17
  450. package/docs/examples/host-minimal-worker/wrangler.jsonc +0 -22
  451. package/docs/handbook/chatgpt-sites/equipment-checkout.md +0 -231
  452. package/docs/handbook/chatgpt-sites/generated-app.md +0 -84
  453. package/docs/handbook/chatgpt-sites/host-reference.md +0 -113
  454. package/docs/handbook/chatgpt-sites/index.md +0 -133
  455. package/docs/handbook/cloudflare/bindings.md +0 -291
  456. package/docs/handbook/cloudflare/conventional-worker.md +0 -166
  457. package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -170
  458. package/docs/handbook/cloudflare/low-level-composition.md +0 -169
  459. package/docs/handbook/cloudflare/public-web.md +0 -144
  460. package/docs/handbook/cloudflare/site-chrome.md +0 -75
  461. package/docs/handbook/examples/commerce-transaction.md +0 -11
  462. package/docs/handbook/examples/commerce.md +0 -11
  463. package/docs/handbook/examples/guarded-api.md +0 -10
  464. package/docs/handbook/examples/intake-form.md +0 -12
  465. package/docs/handbook/examples/intake-hooks.md +0 -11
  466. package/docs/handbook/examples/legal-documents.md +0 -10
  467. package/docs/handbook/examples/procurement-approvals.md +0 -10
  468. package/docs/handbook/examples/publication.md +0 -10
  469. package/docs/handbook/examples/reservation.md +0 -10
  470. package/docs/handbook/start/quickstart-admin.md +0 -52
  471. package/docs/labels.md +0 -71
  472. package/docs/media-uploads.md +0 -5
  473. package/docs/migration-0.1.2.md +0 -13
  474. package/docs/performance-harness.md +0 -228
  475. package/docs/release-process.md +0 -244
  476. package/docs/schema-indexes.md +0 -5
  477. package/docs/sealed-pipeline-ownership.md +0 -101
  478. package/docs/skills/plugin/SKILL.md +0 -132
  479. package/docs/skills/theme/SKILL.md +0 -77
  480. package/docs/spec-only-host-adoption.md +0 -157
  481. package/docs/transaction-patterns.md +0 -5
  482. package/skills/develop/SKILL.md +0 -258
  483. package/skills/install/SKILL.md +0 -112
  484. package/skills/integrate/SKILL.md +0 -60
  485. package/skills/media-gc/SKILL.md +0 -85
  486. package/skills/plugin/SKILL.md +0 -132
  487. package/skills/provision/SKILL.md +0 -194
  488. package/skills/theme/SKILL.md +0 -77
  489. package/skills/update/SKILL.md +0 -51
  490. package/templates/chatgpt-sites/auth.ts +0 -66
  491. package/templates/chatgpt-sites/build.mjs +0 -14
  492. package/templates/chatgpt-sites/env.ts +0 -1
  493. package/templates/chatgpt-sites/mcp.ts +0 -48
  494. package/templates/chatgpt-sites/public-mcp.ts +0 -29
  495. package/templates/chatgpt-sites/smoke-local.mjs +0 -20
@@ -0,0 +1,87 @@
1
+ /** `admin-client.ts`: the types Admin's data hooks bind to, for the application's browser-side `admin.tsx`. */
2
+ import { BUILT_IN_NAV_GROUPS, mcpTools } from "../build/compiler.js";
3
+ import { q } from "../domain/entry.js";
4
+ /**
5
+ * The compatibility test of the application's Admin MCP App (`node --test generated/admin-mcp-app.check.mjs`): it loads the App
6
+ * `mantle bundle` built in the official MCP Apps host and fails on a network request, a console error or an exception. It needs
7
+ * `playwright-core` (a devDependency) and Chrome: `npx playwright-core install chromium`, or `MANTLE_TEST_CHROMIUM=<executable>`.
8
+ * Node strips the types of `admin-descriptors.ts` (Node 22.18 or newer).
9
+ */
10
+ const MCP_APP_TEST = `import { existsSync } from "node:fs";
11
+ import { test } from "node:test";
12
+ import { checkMcpApp } from "@aotter/mantle-admin/testing";
13
+ import { mcpAppCatalogues } from "./admin-descriptors.ts";
14
+
15
+ // \`mantle bundle <entry> dist\` writes the App here; MANTLE_MCP_APP_HTML names another file
16
+ const html = process.env.MANTLE_MCP_APP_HTML ?? "dist/client/admin/mcp-app.html";
17
+
18
+ test("the Admin MCP App works in an MCP host", { timeout: 300_000 }, async () => {
19
+ if (!existsSync(html)) throw new Error(\`\${html} is missing: run \\\`npm run build\\\` first.\`);
20
+ await checkMcpApp({ catalogue: mcpAppCatalogues.admin, html });
21
+ });
22
+ `;
23
+ /** A Schema, View input or Procedure input as a closed object type; one that is only a `$ref` stays open. */
24
+ const closed = (typeOf, schema) => !schema ? "{}" : schema.properties || schema.type === "object" && !schema["$ref"] ? typeOf({ ...schema, additionalProperties: false }) : "{ readonly [key: string]: unknown }";
25
+ /**
26
+ * Types are what the routes take and return, not the Store's rows: an entry's `data` keeps declared field names, an input is
27
+ * what the route validates, a View row is its wire row under the names the browser route gives its columns (an MCP App's
28
+ * `tools/call` returns the SQL names; the hooks rename them with `rowNames`), with `true` when it is an admin MCP tool, else null, which
29
+ * types `pages[].mcp.renders` (types only; nothing ships). The file imports `generated/*` as types only, so nothing of the
30
+ * Worker enters the browser bundle, and it is refused in the Worker's startup graph (`mantle bundle`).
31
+ */
32
+ export function emitAdminClient(plan, typeOf) {
33
+ const tools = mcpTools(plan, "admin"), hasTool = (kind, source) => tools.some(t => t.kind === kind && t.source === source);
34
+ const views = Object.entries(plan.views).filter(([, v]) => v.surface === "admin").sort(([a], [b]) => a < b ? -1 : 1);
35
+ const operations = Object.entries(plan.procedures).filter(([name]) => hasTool("procedure", name)).sort(([a], [b]) => a < b ? -1 : 1);
36
+ const schemas = Object.values(plan.schemas).sort((a, b) => a.name < b.name ? -1 : 1);
37
+ // the scope column belongs to the caller, so an entry's data has no such field
38
+ const dataOf = (s) => {
39
+ const scope = s.scope ? s.names[s.scope] ?? s.scope : undefined, { [scope ?? ""]: _scope, ...properties } = s.schema.properties ?? {};
40
+ // a draft of a publishing collection is only partly validated, so none of its fields is certain to be there
41
+ const required = s.publishing ? [] : s.schema.required?.filter(f => f !== scope);
42
+ const schema = { ...s.schema, properties, ...(required ? { required } : {}) };
43
+ return s.translates ? typeOf(schema) : closed(typeOf, schema);
44
+ };
45
+ // the names the browser route gives a View's columns where they differ from the SQL output names, as it applies them
46
+ const renames = views.map(([, v]) => Object.fromEntries(Object.entries(v.columns ?? {}).flatMap(([key, c]) => {
47
+ const named = key === c.field ? plan.schemas[c.schema].names[c.field] ?? key : key;
48
+ return named !== key ? [[key, named]] : [];
49
+ })));
50
+ // every sidebar group an admin Schema or View places an item in; `defineAdmin` must declare the ones Admin does not have
51
+ // (the generate-time check of `uiSchema.nav.group` is this type)
52
+ const placed = (ui) => { const group = ui?.["nav"]?.group; return typeof group === "string" ? [group] : []; };
53
+ const used = [...new Set([...schemas.flatMap(s => placed(s.uiSchema)), ...views.flatMap(([, v]) => placed(v.uiSchema))])].sort();
54
+ const custom = used.filter(group => !BUILT_IN_NAV_GROUPS.has(group));
55
+ const union = (names) => names.length ? names.map(name => q(name)).join(" | ") : "never";
56
+ // whether an admin View is an MCP tool, which a page declared for an MCP App (`pages[].mcp.renders`) must have
57
+ const toolOf = (name) => hasTool("view", name) || null;
58
+ const rows = views.map(([name], n) => `import type { WireRow as View${n}Wire } from "./views/${encodeURIComponent(name)}.js";`);
59
+ const sql = operations.some(([, p]) => "sql" in p.handler);
60
+ return { "admin-mcp-app.check.mjs": MCP_APP_TEST, "admin-client.ts": [
61
+ "// Browser only: import this from src/admin.tsx, never from the Worker (`mantle bundle` refuses it there).",
62
+ 'import type { AdminMeta } from "@aotter/mantle-admin/app";',
63
+ 'import { createAdminHooks } from "@aotter/mantle-admin/hooks";',
64
+ ...(sql ? ['import type { Output } from "@aotter/mantle/contracts";'] : []),
65
+ ...rows,
66
+ "",
67
+ "type Renamed<Row, Names extends { readonly [from: string]: string }> = { [K in keyof Row as K extends keyof Names ? Names[K] : K]: Row[K] };",
68
+ ...views.map((_, n) => `type View${n}Row = Renamed<View${n}Wire, ${q(renames[n])}>;`),
69
+ "",
70
+ "/** Every sidebar group the manifest places an item in. */",
71
+ `export type ManifestNavGroup = ${union(used)};`,
72
+ "",
73
+ "/** The input and row of each admin View, the `data` of each collection's entries, and the input and output of each admin operation. */",
74
+ "export interface AppAdminTypes {",
75
+ ` readonly views: { ${views.map(([name, v], n) => `readonly ${q(name)}: { readonly input: ${closed(typeOf, v.input)}; readonly row: View${n}Row; readonly mcp: ${q(toolOf(name))} }`).join("; ")} };`,
76
+ ` readonly collections: { ${schemas.map(s => `readonly ${q(s.name)}: { readonly data: ${dataOf(s)} }`).join("; ")} };`,
77
+ ` readonly operations: { ${operations.map(([name, p]) => `readonly ${q(name)}: { readonly input: ${closed(typeOf, p.input)}; readonly output: ${"sql" in p.handler ? "Output" : "unknown"} }`).join("; ")} };`,
78
+ ` readonly navGroups: ${union(custom)};`,
79
+ "}",
80
+ "",
81
+ "/** Pass to `defineAdmin(admin)`: the column names a View's rows carry in the browser, for an MCP App to match. */",
82
+ `export const admin: AdminMeta<AppAdminTypes> = { rowNames: ${q(Object.fromEntries(views.flatMap(([name], n) => Object.keys(renames[n]).length ? [[name, renames[n]]] : [])))} };`,
83
+ "",
84
+ "export const { useView, useEntries, useEntry, useOperation, useCollectionTotal, useMe, useBadges } = createAdminHooks<AppAdminTypes>();",
85
+ "",
86
+ ].join("\n") };
87
+ }
@@ -0,0 +1,107 @@
1
+ /** Build-time projections only; generated exports are static, independently tree-shakable data. */
2
+ import { isPrimaryCollection, mcpTools, mcpToolNameSegment, toolTitle, collectionOf, developerConsole, rowPreconditions, appCatalog, entrySchema } from "../build/compiler.js";
3
+ import { q } from "../domain/entry.js";
4
+ import { advertised } from "./validation.js";
5
+ import { publishingAnnotations, publishingInput, publishingTransitions } from "./publishing.js";
6
+ import { versioned } from "./tool-version.js";
7
+ export function emitAdminDescriptors({ plan, admin = false }, diagnostics) {
8
+ const schemaList = Object.values(plan.schemas);
9
+ const placed = [...schemaList.map(s => ["schemas/" + s.name, s.uiSchema, s]), ...Object.entries(plan.views).map(([name, v]) => ["views/" + name, v.uiSchema, null])];
10
+ for (const [path, ui, s] of placed) {
11
+ const nav = (ui?.["nav"] ?? {}), group = nav["group"];
12
+ // a child folded under its parent, or a translation on its parent's language tabs, has no sidebar entry to place
13
+ if (s && !isPrimaryCollection(s, schemaList))
14
+ for (const key of ["group", "order", "hidden"])
15
+ if (nav[key] !== undefined)
16
+ diagnostics.push({ code: "SCHEMA_UI_INVALID", path: `${path}/uiSchema/nav/${key}`, message: s.translates ? `Schema '${s.name}' has no sidebar entry to place: it is a translation, shown on its parent's language tabs.` : `Schema '${s.name}' has no sidebar entry to place: it is folded under its parent${collectionOf(s, schemaList).parent?.parentField === "id" ? "; nav.standalone: true lists it in the sidebar." : " and opens from its parent's workbench (standalone needs a required id reference)."}` });
17
+ }
18
+ if (diagnostics.length)
19
+ return {};
20
+ const schemas = Object.values(plan.schemas);
21
+ const allCollections = Object.fromEntries(schemas.map(s => [s.name, { ...collectionOf(s, schemas), ...(admin ? { scopeField: s.scope ? s.names[s.scope] ?? s.scope : null, exportFields: Object.keys(s.fields).filter(f => f !== s.scope && s.fields[f] !== "geo").map(f => s.names[f] ?? f) } : {}) }]));
22
+ const viewNames = Object.create(null);
23
+ const views = Object.entries(plan.views).filter(([, v]) => v.surface === "admin").map(([name, v]) => {
24
+ const columns = Object.fromEntries(Object.entries(v.columns ?? {}).map(([key, column]) => {
25
+ const schema = plan.schemas[column.schema], field = schema.names[column.field] ?? column.field;
26
+ const output = key === column.field ? field : key;
27
+ (viewNames[name] ??= Object.create(null))[key] = output;
28
+ return [output, { schema: schema.name, field }];
29
+ }));
30
+ const list = (v.uiSchema?.list ?? {});
31
+ return { name, title: v.title ?? null, description: v.description ?? null, input: v.input ?? null, list: { columns: list.columns ?? [], searchFields: list.searchFields ?? [], filterFields: list.filterFields ?? [] }, columns, ...(v.uiSchema?.nav ? { nav: v.uiSchema.nav } : {}) };
32
+ });
33
+ const adminTools = mcpTools(plan, "admin"), toolNames = new Set(adminTools.map(t => t.name));
34
+ const operations = adminTools.filter(t => t.kind === "procedure").map(t => {
35
+ const p = plan.procedures[t.source];
36
+ const preconditions = p.target && "sql" in p.handler ? rowPreconditions(p.handler.sql.stmts, p.target.schema, p.target.id, plan.schemas[p.target.schema.toLowerCase()]?.schema ?? {}) : [];
37
+ return { ...(preconditions.length ? { preconditions } : {}), name: t.source, title: p.title ?? null, description: p.description ?? null, input: p.input, uiSchema: p.uiSchema ?? null, interactions: p.target ? [{ collection: p.target.schema, bind: [{ input: p.target.id, field: "id" }], ...(p.target.version ? { version: p.target.version } : {}), mutates: true }] : [] };
38
+ });
39
+ const authorizations = { views: Object.fromEntries(views.map(v => [v.name, plan.views[v.name].requires ?? null])), procedures: Object.fromEntries(Object.entries(plan.procedures).map(([name, p]) => [name, p.requires ?? null])) };
40
+ const adminCatalogue = appCatalog(plan, "admin");
41
+ const catalogues = { public: appCatalog(plan, "public"), admin: { views: Object.fromEntries(Object.entries(adminCatalogue.views).map(([name, v]) => [name, { ...v, actions: [...v.actions] }])), actions: { ...adminCatalogue.actions } } };
42
+ const snapshot = developerConsole(plan);
43
+ const webmcp = { tools: adminTools.map(({ name, source, kind, title, description, inputSchema, outputSchema, annotations }) => ({ target: { kind, name: source }, ...versioned({ name, ...(title ? { title: (kind === "view" ? plan.views[source] : plan.procedures[source]).title ?? title } : {}), ...advertised(inputSchema, (kind === "view" ? plan.views[source] : plan.procedures[source]).description ?? description), ...(outputSchema ? { outputSchema } : {}), ...(annotations ? { annotations } : {}) }) })), routes: Object.fromEntries(adminTools.flatMap(t => {
44
+ if (t.kind === "view")
45
+ return [[t.name, { path: "/admin/views/" + encodeURIComponent(t.source) }]];
46
+ const target = plan.procedures[t.source].target;
47
+ return target ? [[t.name, { path: "/admin/c/" + encodeURIComponent(plan.schemas[target.schema.toLowerCase()]?.name ?? target.schema), entry: true }]] : [];
48
+ })) };
49
+ // These are the actual generated transitions, not authored ref handlers or SQL IR.
50
+ for (const schema of schemas.filter(s => s.publishing))
51
+ for (const [verb] of publishingTransitions) {
52
+ const name = schema.name + "." + verb, tool = mcpToolNameSegment(verb + "_" + schema.name);
53
+ if (toolNames.has(tool)) {
54
+ diagnostics.push({ code: "GENERATE_UNSUPPORTED", path: "schemas/" + schema.name, message: "Generated publishing tool '" + tool + "' conflicts with an admin tool." });
55
+ continue;
56
+ }
57
+ toolNames.add(tool);
58
+ const action = { capability: tool, inputSchema: publishingInput, bind: [{ input: "id", field: "id" }], version: "expectedVersion", mutates: true };
59
+ catalogues.admin.actions[tool] = action;
60
+ for (const t of adminTools.filter(t => t.kind === "view"))
61
+ if (entrySchema(plan, plan.views[t.source]) === schema.name.toLowerCase())
62
+ catalogues.admin.views[t.name].actions.push(tool);
63
+ authorizations.procedures[name] = { auth: { all: ["ctx.admin"] } };
64
+ const description = verb + " an entry in " + schema.name + ".";
65
+ webmcp.tools.push({ target: { kind: "procedure", name }, ...versioned({ name: tool, title: toolTitle(verb + "_" + schema.name), description, inputSchema: publishingInput, annotations: publishingAnnotations(verb) }) });
66
+ webmcp.routes[tool] = { path: "/admin/c/" + encodeURIComponent(schema.name), entry: true };
67
+ snapshot.interfaces.callable.push({ kind: "procedure", name: tool, target: name, surface: "admin", audience: "admin", title: null, description, input: publishingInput, output: null, trigger: null });
68
+ }
69
+ const data = { allCollections, views, operations, fieldNames: Object.fromEntries(schemas.map(s => [s.name, s.names])), viewNames, authorizations, developerConsole: snapshot, mcpAppCatalogues: catalogues, webmcp };
70
+ return { "admin-descriptors.ts": "// Generated static Admin metadata. Import only the exports the application uses.\n" + literals(data) + "\nexport const collections = [" + schemas.filter(s => !s.translates).map(s => "allCollections[" + q(s.name) + "]").join(",") + "] as const;\nexport const bootstrap = {collections, allCollections, operations, views, webmcp, developerConsole} as const;\n" };
71
+ }
72
+ /**
73
+ * Each export as a JSON literal; a subtree of 40 or more bytes that would be printed more than once is printed once, as a
74
+ * module-private constant the exports reference (a bundler keeps only those an imported export reaches).
75
+ */
76
+ function literals(exports) {
77
+ const data = JSON.parse(q(exports)), count = new Map(), hoisted = new Map(), constants = [];
78
+ const each = (value, visit) => { if (value && typeof value === "object")
79
+ for (const child of Object.values(value))
80
+ if (child && typeof child === "object")
81
+ visit(q(child), child); };
82
+ const tally = (json, value, by = 1) => { if (json.length >= 40)
83
+ count.set(json, (count.get(json) ?? 0) + by); each(value, (json, child) => tally(json, child, by)); };
84
+ for (const value of Object.values(data))
85
+ each(value, tally);
86
+ // Longest first: once a subtree is printed once, what it contains occurs (copies - 1) times fewer per occurrence in it.
87
+ for (const json of [...count.keys()].sort((a, b) => b.length - a.length)) {
88
+ const copies = count.get(json);
89
+ if (copies > 1) {
90
+ hoisted.set(json, "");
91
+ each(JSON.parse(json), (inner, child) => tally(inner, child, 1 - copies));
92
+ }
93
+ }
94
+ let named = 0;
95
+ const print = (value) => !value || typeof value !== "object" ? q(value) : Array.isArray(value) ? "[" + value.map(reference).join(",") + "]" : "{" + Object.entries(value).map(([key, child]) => (key === "__proto__" ? "[" + q(key) + "]" : q(key)) + ":" + reference(child)).join(",") + "}";
96
+ const reference = (value) => {
97
+ const json = q(value);
98
+ if (hoisted.get(json) === "") {
99
+ const name = "$" + named++;
100
+ hoisted.set(json, name);
101
+ constants.push("const " + name + " = " + print(value) + " as const;");
102
+ }
103
+ return hoisted.get(json) ?? print(value);
104
+ };
105
+ const lines = Object.entries(data).map(([name, value]) => "export const " + name + " = " + print(value) + " as const;");
106
+ return [...constants, ...lines].join("\n");
107
+ }
@@ -0,0 +1,166 @@
1
+ import { userReferences } from "../domain/userReferences.js";
2
+ /** Canonical lowered programs become ordinary target-specific TypeScript, never a runtime plan. */
3
+ import { parseNumeric, tableOutputs } from "../build/compiler.js";
4
+ import { boundaryType, nativeType, outputType, wireType } from "../domain/columns.js";
5
+ import { databaseImport, driverFor, entry, exportName, generatedFiles, isSqlite as sqlite, q } from "../domain/entry.js";
6
+ import { authLines as auth } from "../domain/input.js";
7
+ import { recipesOf } from "../domain/recipes.js";
8
+ import { emitTtl } from "./ttl.js";
9
+ import { emitPublishing } from "./publishing.js";
10
+ import { emitStores } from "./stores.js";
11
+ import { emitPaging } from "./paging.js";
12
+ import { emitWeb } from "./web.js";
13
+ import { emitMcp } from "./mcp.js";
14
+ import { emitValidation } from "./validation.js";
15
+ import { refuseDeclarations } from "./refusals.js";
16
+ import { emitHandlers } from "./handlers.js";
17
+ import { emitAdminDescriptors } from "./adminDescriptors.js";
18
+ import { emitAdminClient } from "./adminClient.js";
19
+ import { emitSite } from "./site.js";
20
+ import { emitTranslations } from "./translations.js";
21
+ import { emitAdminBindings } from "./adminBindings.js";
22
+ import { emitRest } from "./rest.js";
23
+ const typeOf = (s) => {
24
+ if (!s)
25
+ return "unknown";
26
+ if (s.nullable === true)
27
+ return `${typeOf({ ...s, nullable: false })} | null`;
28
+ if (s.enum)
29
+ return s.enum.map(v => q(v)).join(" | ") || "never";
30
+ if (s.const !== undefined)
31
+ return q(s.const);
32
+ if (Array.isArray(s.type))
33
+ return s.type.map(type => typeOf({ ...s, type })).join(" | ") || "never";
34
+ switch (s.type) {
35
+ case "string": return "string";
36
+ case "integer":
37
+ case "number": return "number";
38
+ case "boolean": return "boolean";
39
+ case "null": return "null";
40
+ case "array": return `ReadonlyArray<${Array.isArray(s.items) ? "unknown" : typeOf(s.items)}>`;
41
+ case "object": return `{ ${Object.entries(s.properties ?? {}).map(([key, child]) => `readonly ${q(key)}${s.required?.includes(key) ? "" : "?"}: ${typeOf(child)};`).join(" ")} ${s.additionalProperties !== false ? "readonly [key: string]: unknown;" : ""} }`;
42
+ default: return "unknown";
43
+ }
44
+ };
45
+ export function generate(input) {
46
+ const { target, plan, lowered, viewKeys, newId, site, admin, paging } = input;
47
+ const diagnostics = [];
48
+ refuseDeclarations({ plan }, diagnostics);
49
+ const refuse = (path, message) => { diagnostics.push({ code: "GENERATE_UNSUPPORTED", path, message }); };
50
+ const isSqlite = sqlite(target);
51
+ const user = userReferences(plan, isSqlite);
52
+ const expected = isSqlite ? "@aotter/mantle/d1" : "@aotter/mantle/postgres";
53
+ if (lowered.dialect.name !== expected || plan.dialect.name !== expected)
54
+ refuse("target", `Target '${target}' needs canonical '${expected}' compilation and lowering.`);
55
+ const driver = driverFor(target);
56
+ const queryInputType = (schema, statements) => {
57
+ if (!schema?.properties)
58
+ return typeOf(schema);
59
+ const binds = statements.flatMap(s => s.binds).filter(b => b.k === "input");
60
+ const properties = Object.entries(schema.properties).map(([name, field]) => {
61
+ const bind = binds.find(b => b.k === "input" && name.toLowerCase() === String(b.name).toLowerCase());
62
+ if (!bind || bind.k !== "input")
63
+ return `readonly ${q(name)}${schema.required?.includes(name) ? "" : "?"}: ${typeOf(field)};`;
64
+ const type = String(bind.type);
65
+ const driverType = type === "json" ? typeOf(field) : type === "date" ? "string" : type === "timestamptz" ? "string | Date" : isSqlite
66
+ ? type === "bool" || parseNumeric(type) ? "number" : typeOf(field)
67
+ : parseNumeric(type) ? "number | string" : typeOf(field);
68
+ const nullable = field.nullable === true || Array.isArray(field.type) && field.type.includes("null") || !schema.required?.includes(name);
69
+ // D1 cannot bind undefined. Bound fields are explicit, null represents absence.
70
+ return `readonly ${q(name)}: ${driverType}${nullable ? " | null" : ""};`;
71
+ });
72
+ return `{ ${properties.join(" ")} ${schema.additionalProperties !== false ? "readonly [key:string]: unknown;" : ""} }`;
73
+ };
74
+ const inputFields = (schema, path) => {
75
+ const fields = Object.keys(schema?.properties ?? {});
76
+ if (new Set(fields.map(field => field.toLowerCase())).size !== fields.length)
77
+ refuse(path, "Input properties must have unique case-insensitive names. Rename input properties to be unique without regard to case.");
78
+ return fields;
79
+ };
80
+ // The dialect functions the bind recipes call, so a bundle carries only those (`binds` takes this object).
81
+ const values = new Set();
82
+ /** A statement as a constant: its text and how to bind it. */
83
+ const step = (s, path, fields, row = false) => {
84
+ const recipes = recipesOf(s, fields, message => refuse(path, message));
85
+ for (const r of recipes) {
86
+ if (r.k === "now" || r.k === "cutoff")
87
+ values.add(r.k);
88
+ else if (r.k === "input" && r.type)
89
+ values.add(r.type === "date" ? "date" : "timestamp");
90
+ }
91
+ return `{ sql: ${q(s.sql)}, binds: ${q(recipes)}${row ? ", row: true" : ""} }`;
92
+ };
93
+ const declarations = [], views = [], procedures = [], refProcedures = [];
94
+ const entries = {};
95
+ const base = "@aotter/mantle/";
96
+ const refNames = emitHandlers(plan, target !== "cloudflare-d1", typeOf, declarations, refProcedures);
97
+ const hasHandlers = refNames.length > 0;
98
+ for (const [index, [name, view]] of Object.entries(plan.views).sort().entries()) {
99
+ const path = `views/${name}`, fn = `view${index}`;
100
+ const modes = lowered.views[name];
101
+ const mode = view.surface === "public" ? "public" : "caller";
102
+ const c = modes?.[mode], trusted = modes?.trusted;
103
+ if (!c || (mode === "caller" && !trusted)) {
104
+ refuse(path, "Every reachable visibility mode must have canonical lowered SQL.");
105
+ continue;
106
+ }
107
+ const keys = Object.hasOwn(viewKeys, name) ? viewKeys[name] : undefined;
108
+ if (!keys || new Set(keys).size !== keys.length) {
109
+ refuse(path, "Dynamic or repeated View outputs need an explicit generated row contract. Give each SELECT output a unique explicit alias.");
110
+ continue;
111
+ }
112
+ const output = [], direct = tableOutputs(view, plan.schemas);
113
+ for (const key of keys) {
114
+ // A table column read unchanged keeps its NOT NULL (and gives a native column such as `id` its type); anything else may be NULL.
115
+ const type = outputType(plan, name, key) ?? direct[key]?.type;
116
+ // Declared timestamps and dates arrive formatted by the SQL itself (domain/wireScalars).
117
+ output.push(`readonly ${q(key)}: ${nativeType(type, isSqlite, direct[key]?.notNull)};`);
118
+ }
119
+ const fieldNames = inputFields(view.input, path + "/input");
120
+ const callerStep = step(c, path, fieldNames), trustedStep = mode === "caller" ? step(trusted, path, fieldNames) : callerStep;
121
+ declarations.push(`export type View${index}Input = ${queryInputType(view.input ?? { type: "object", additionalProperties: false }, [c, ...(trusted ? [trusted] : [])])};`, `export type View${index}Row = { ${output.join(" ")} };`, `export type View${index}WireRow = { ${keys.map(key => `readonly ${q(key)}: ${boundaryType(plan, name, key) ? wireType(boundaryType(plan, name, key), direct[key]?.notNull) : `View${index}Row[${q(key)}]`};`).join(" ")} };`, `const ${fn}Caller = ${callerStep} as const;`, ...(trustedStep !== callerStep ? [`const ${fn}Trusted = ${trustedStep} as const;`] : []), `async function ${fn}(db: ${isSqlite ? "Database" : "Unsafe"}, caller: Caller, raw: View${index}Input): Promise<View${index}Row[]> {`, ...auth(view.requires, path), ...(user.used ? [' USER.modeOf(caller);'] : []), ` const m = ${trustedStep !== callerStep ? `${user.used ? 'USER.modeOf(caller) === "trusted"' : 'caller.kind === "system"'} ? ${fn}Trusted : ` : ""}${fn}Caller;`, ` return DRIVER.read(db, ${user.used ? "USER.scoped(m.sql, caller)" : "m.sql"}, binds(D, m.binds, caller, raw));`, "}");
122
+ views.push(`[${q(name)}]: ${fn}`);
123
+ declarations.push(`export { ${fn} as ${exportName("view", name)} };`);
124
+ entries[`views/${encodeURIComponent(name)}.ts`] = entry("view", name, `View${index}Input as Input, View${index}Row as Row, View${index}WireRow as WireRow`);
125
+ }
126
+ for (const [index, [name, procedure]] of Object.entries(plan.procedures).sort().entries()) {
127
+ const path = `procedures/${name}`, fn = `procedure${index}`;
128
+ if (!("sql" in procedure.handler))
129
+ continue;
130
+ const modes = lowered.procedures[name];
131
+ if (!modes?.caller || !modes.trusted) {
132
+ refuse(path, "Both caller and trusted SQL must be lowered.");
133
+ continue;
134
+ }
135
+ if ([...modes.caller, ...modes.trusted].some(s => s.publish || s.schema && plan.schemas[s.schema]?.publishing))
136
+ refuse(path, "A SQL Procedure cannot write a publishing Schema: create and edit its rows from a ref handler through the Store (`ctx.store.collection(...)`), and change status with the generated `<schema>.publish` / `.unpublish` / `.archive` transitions.");
137
+ const fieldNames = inputFields(procedure.input, path + "/input");
138
+ const steps = (statements) => `[${statements.map(s => step(s, path, fieldNames, s.kind === "row")).join(", ")}] as const`;
139
+ const same = steps(modes.caller) === steps(modes.trusted);
140
+ declarations.push(`export type Procedure${index}Input = ${queryInputType(procedure.input, [...modes.caller, ...modes.trusted])};`, `const ${fn}Caller = ${steps(modes.caller)};`, ...(same ? [] : [`const ${fn}Trusted = ${steps(modes.trusted)};`]), `async function ${fn}(db: Database, caller: Caller, raw: Procedure${index}Input): Promise<Output> {`, ...(modes.caller.some(s => s.kind !== "read") ? [` if ("authenticatedAs" in caller && caller.authenticatedAs === "snapshot") (await import("@aotter/mantle/host-caller")).checkFresh(caller, false, ${q(path)});`] : []), ...auth(procedure.requires, path), ...(user.used ? [' USER.modeOf(caller);'] : []), ...(procedure.trustedOnly ? [` deny(caller, USER.modeOf(caller) === "trusted", ${q(path)});`] : []), ` return { results: await DRIVER.procedure(db, bound(D, ${user.used ? "USER.scopedAll(" : ""}${same ? "" : `${user.used ? 'USER.modeOf(caller) === "trusted"' : 'caller.kind === "system"'} ? ${fn}Trusted : `}${fn}Caller${user.used ? ", caller)" : ""}, caller, raw), ${q(path)}) };`, "}");
141
+ procedures.push(`[${q(name)}]: ${fn}`);
142
+ declarations.push(`export { ${fn} as ${exportName("procedure", name)} };`);
143
+ entries[`procedures/${encodeURIComponent(name)}.ts`] = entry("procedure", name, `Procedure${index}Input as Input`, `export type { Output } from "${base}contracts";\n`);
144
+ }
145
+ emitTtl(plan, isSqlite, declarations, procedures, entries, refuse);
146
+ emitPublishing(plan, isSqlite, declarations, procedures, entries, values, refuse);
147
+ const stores = emitStores(plan.schemas, isSqlite, newId, refuse, hasHandlers);
148
+ const surfaces = { ...emitPaging(plan, lowered, target, refuse, paging), ...emitPaging(plan, input.wireLowered ?? lowered, target, refuse, paging, true), ...emitValidation(plan, refuse), ...emitRest(plan, lowered, target, input.description), ...emitWeb(plan, lowered, target), ...emitMcp(plan, lowered, target, input.description, refuse, !!site), ...emitAdminDescriptors({ plan, admin }, diagnostics), ...emitAdminBindings(input, refuse), ...(admin ? emitAdminClient(plan, typeOf) : {}), ...emitSite(target, site, refuse, Object.keys(plan.schemas)), ...emitTranslations(plan, target) };
149
+ if (diagnostics.length)
150
+ return { ok: false, diagnostics };
151
+ const storeProcedures = hasHandlers ? "storeProcedures" : "procedures";
152
+ const code = [...(Object.keys(plan.access?.userFields ?? {}).length ? [`export const userFields = ${q(plan.access.userFields)} as const;`] : []), ...declarations, `export const views = { ${views.join(", ")} };`, `export const procedures = { ${procedures.join(", ")} };`, ...(stores ? [
153
+ `export type Tables = ${stores.tables};`, `const RULES = ${stores.rules} as const;`,
154
+ ...(hasHandlers ? [`const storeProcedures = { ${[...procedures, ...refProcedures].join(", ")} };`] : []),
155
+ `const initStore = () => createStore<Database, Tables, typeof DRIVER, { views: typeof views; procedures: typeof ${storeProcedures} }${hasHandlers ? ", { handlers: Handlers }" : ""}>(${user.used ? "{...DIALECT.dialect, ...USER_VALUES}" : "DIALECT.dialect"}, DRIVER, RULES, ${stores.facts}, { views, procedures: ${storeProcedures} });`,
156
+ `let makeStore: ReturnType<typeof initStore>;`,
157
+ `export function store${hasHandlers ? "<const H extends Handlers>" : ""}(db: Database, caller: Caller${hasHandlers ? ", options: { handlers: H & Record<Exclude<keyof H, keyof Handlers>, never> }" : ", _options?: { handlers?: Handlers }"}): ReturnType<typeof makeStore> { return (makeStore ??= initStore())(db, caller${hasHandlers ? ", options" : ""}); }`,
158
+ ] : [])].join("\n");
159
+ const dialect = `${base}dialect/${isSqlite ? "sqlite" : "postgres"}`;
160
+ const contracts = [...(target.endsWith("postgres") ? ["type Postgres as Database", ...(views.length ? ["type Unsafe"] : [])] : []), "type Caller", ...(hasHandlers ? ["ContractError"] : []), ...(/^ deny\(/m.test(code) ? ["deny", ...(/\bisAdmin\(/.test(code) ? ["isAdmin"] : []), ...(/\bisUser\(/.test(code) ? ["isUser"] : [])] : []), ...(procedures.length ? ["type Output"] : [])];
161
+ const dbType = isSqlite ? databaseImport(target) : "";
162
+ return { ok: true, refProcedures: refNames, files: generatedFiles({ ...surfaces, ...entries, "generated.ts": ["// Generated native application SQL. No runtime plan or interpreter.", user.code, dbType, `import { ${contracts.join(", ")} } from "${base}contracts";`,
163
+ ...(values.size || stores ? [`import * as DIALECT from "${dialect}";`] : []), `import * as DRIVER from "${base}driver/${driver}";`, ...((names => names.length ? [`import { ${names.join(", ")} } from "${base}store";`] : [])([...(stores ? ["createStore"] : []), ...(hasHandlers && target !== "cloudflare-d1" ? ["handlerTransaction"] : [])])),
164
+ `import { ${procedures.length ? "binds, bound" : "binds"} } from "${base}binds";`,
165
+ `const D = { ${[...values].sort().map(v => `${v}: DIALECT.${v}`).concat(user.values ? [user.values] : []).join(", ")} };`, code].filter(Boolean).join("\n") + "\n" }) };
166
+ }
@@ -0,0 +1,21 @@
1
+ import { authLines as auth } from "../domain/input.js";
2
+ import { q } from "../domain/entry.js";
3
+ export function emitHandlers(plan, interactive, typeOf, declarations, procedures) {
4
+ const refs = Object.entries(plan.procedures).sort().map(([name, procedure], index) => ({ name, procedure, index })).filter(({ procedure }) => "ref" in procedure.handler);
5
+ // An empty contract keeps app wiring (`handlers: Handlers = {}`) identical whether or not any ref Procedure exists.
6
+ if (!refs.length) {
7
+ declarations.push("export interface Handlers {}");
8
+ return [];
9
+ }
10
+ declarations.push('export type HandlerStore = Pick<ReturnType<typeof makeStore>, "collection" | "views" | "procedures">;',
11
+ // D1 has no interactive transactions, so its handlers get no `transaction`: using one there fails typecheck.
12
+ `export interface HandlerContext { readonly caller: Caller; readonly db: Database; readonly store: HandlerStore;${interactive ? " readonly transaction: <R>(fn: (ctx: { readonly store: HandlerStore; readonly db: Database }) => Promise<R>) => Promise<R>;" : ""} }`, "export interface Handlers {", ...refs.map(({ name, index }) => ` readonly [${q(name)}]: (ctx: HandlerContext, input: Procedure${index}Input) => Promise<Procedure${index}Output>;`), "}");
13
+ for (const { name, procedure, index } of refs) {
14
+ const path = `procedures/${name}`, fn = `procedure${index}`;
15
+ declarations.push(`export type Procedure${index}Input = ${typeOf(procedure.input ?? { type: "object", additionalProperties: false })};`, `export type Procedure${index}Output = ${typeOf(procedure.output)};`, `async function ${fn}(db: Database, caller: Caller, raw: Procedure${index}Input, context: { handlers: Handlers; store: HandlerContext["store"] }): Promise<Procedure${index}Output> {`, ` if ("authenticatedAs" in caller && caller.authenticatedAs === "snapshot") (await import("@aotter/mantle/host-caller")).checkFresh(caller, false, ${q(path)});`, ...auth(procedure.requires, path), " const handlers = context?.handlers;", ` const handler = handlers && Object.hasOwn(handlers, ${q(name)}) ? handlers[${q(name)}] : undefined;`, ` if (!handler) throw new ContractError("HANDLER_NOT_REGISTERED", "Procedure handler is not registered.", ${q(path)});`,
16
+ // TODO #1470: REST/MCP use shared generated validators at the boundary; bare calls stay typed.
17
+ ` return handler({ caller, db, store: context.store${interactive ? ", transaction: handlerTransaction(context.store, db)" : ""} }, raw);`, "}");
18
+ procedures.push(`[${q(name)}]: ${fn}`);
19
+ }
20
+ return refs.map(({ name }) => name);
21
+ }
@@ -0,0 +1,17 @@
1
+ import { emitValidation } from "./validation.js";
2
+ import { mediaDefinitions } from "../domain/generated-tools.js";
3
+ export { mediaDefinitions };
4
+ /** `definitions` are the versioned media tools, computed once by the MCP emitter. */
5
+ export function emitMcpMedia(definitions, refuse) {
6
+ const inputs = Object.fromEntries(definitions.map(tool => [tool.name, { input: tool.inputSchema }]));
7
+ const checks = Object.fromEntries(Object.entries(emitValidation({ views: {}, procedures: inputs }, refuse)).map(([path, code]) => [path.replace("validation/procedures/", "validation/media/"), code]));
8
+ const invoke = { policy: 'async (db,caller) => ({purposes:(await site(db,caller).read()).media.purposes})', create: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).createUpload(input)', commit: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).commitUpload(input.uploadGroupId as string,input)', list: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).list(input)', get: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).get(input.id as string)', update: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).update(input.id as string,input)', delete: '(db,caller,input:Record<string,unknown>) => site(db,caller).media(storage()).delete(input.id as string)' };
9
+ return { ...checks, "mcp-media.ts": `import { site } from "./site.js";
10
+ import type { MediaStorage } from "@aotter/mantle/site";
11
+ import type { Tool } from "@aotter/mantle/mcp";
12
+ ${definitions.map((tool, i) => `import { validate as v${i} } from "./validation/media/${tool.name}.js";`).join("\n")}
13
+ export function mediaTools<Handlers = never>(storage: () => MediaStorage): Tool<Parameters<typeof site>[0], Handlers>[] { return [
14
+ ${definitions.map(({ action, ...tool }, i) => `{...${JSON.stringify({ ...tool, ...(tool.annotations.readOnlyHint ? { readOnly: true } : {}) })},surface:"admin",closed:true,validate:v${i},run:${invoke[action]}}`).join(",\n")}
15
+ ]; }
16
+ ` };
17
+ }
@@ -0,0 +1,100 @@
1
+ /** Emit the selected MCP boundary; the official transport loads on its first request. */
2
+ import { mcpToolNameSegment, procedureAnnotations, toolTitle, viewAnnotations } from "../build/compiler.js";
3
+ import { emitMcpMedia, mediaDefinitions } from "./mcp-media.js";
4
+ import { advertised, emitValidation } from "./validation.js";
5
+ import { databaseImport, literal, q } from "../domain/entry.js";
6
+ import { authLines, conversions } from "../domain/input.js";
7
+ import { publishingAnnotations, publishingInput } from "./publishing.js";
8
+ import { versioned } from "./tool-version.js";
9
+ import { boundaryPage, outputConversions } from "../domain/columns.js";
10
+ export function emitMcp(plan, lowered, target, description, refuse, media = false) {
11
+ if (!Object.values(plan.views).some(view => view.surface !== "internal") && !Object.values(plan.triggers).some(trigger => trigger.source.kind === "mcp"))
12
+ return {};
13
+ const reads = [];
14
+ const imports = plan.access ? ['import { userBinds } from "@aotter/mantle/user-fields";'] : [], tools = [];
15
+ const mediaTools = media ? mediaDefinitions().map(({ action, ...tool }) => ({ action, ...versioned({ ...tool, title: toolTitle(tool.name) }) })) : [];
16
+ const files = media ? emitMcpMedia(mediaTools, refuse) : {};
17
+ let hasRef = false, hasWire = false;
18
+ const names = new Set(mediaTools.map(tool => "admin:" + tool.name));
19
+ const guard = (requires, path, binds = [], trustedOnly = false) => {
20
+ const lines = authLines(requires, path, binds, !target.endsWith("postgres"), trustedOnly);
21
+ return lines.length ? `, check: (caller) => {${lines.join("")}}` : "";
22
+ };
23
+ const refs = Object.values(plan.procedures).some(p => "ref" in p.handler);
24
+ if (Object.values(plan.procedures).some(p => p.trustedOnly) || [...Object.values(plan.views), ...Object.values(plan.procedures)].some(p => p.requires?.auth?.all.length) || Object.values(plan.schemas).some(s => s.publishing))
25
+ imports.push('import { deny, isAdmin, isUser } from "@aotter/mantle/contracts";');
26
+ const add = (name, kind, surface, operation = kind === "views" ? plan.views[name] : plan.procedures[name], tool = mcpToolNameSegment(name)) => {
27
+ if (kind === "views")
28
+ reads.push({ name: tool, surface });
29
+ const ref = kind === "procedures" && !!plan.procedures[name] && "ref" in plan.procedures[name].handler;
30
+ hasRef ||= ref;
31
+ const statements = kind === "views" ? [lowered.views[name]?.public ?? lowered.views[name]?.caller] : lowered.procedures[name]?.caller ?? [];
32
+ const binds = statements.flatMap(statement => statement?.binds ?? []);
33
+ const index = tools.length, file = encodeURIComponent(name);
34
+ if (kind === "views")
35
+ imports.push(`import { validate as validate${index} } from "./validation/${kind}/${file}.js";`);
36
+ if (kind === "views") {
37
+ const module = `./${outputConversions(plan, name, target).some(([, type]) => type === "int") ? "paged/wire/views" : "paged/views"}/${file}.js`;
38
+ imports.push(`const run${index}: typeof import(${q(module)}).default = async (...args) => (await import(${q(module)})).default(...args);`);
39
+ }
40
+ // The kernel adds a View tool's `limit` and `cursor` paging properties.
41
+ const { inputSchema, description: about } = advertised(kind === "views" ? operation.input ?? { type: "object", additionalProperties: false } : operation.input, operation.description ?? operation.title ?? name), schema = literal(inputSchema);
42
+ if (kind === "views" && ("limit" in (operation.input?.properties ?? {}) || "cursor" in (operation.input?.properties ?? {})))
43
+ refuse(`views/${name}`, "MCP View input reserves limit and cursor for paging.");
44
+ if (names.has(surface + ":" + tool))
45
+ refuse(`${kind}/${name}`, `MCP tool name ${tool} collides on ${surface}.`);
46
+ names.add(surface + ":" + tool);
47
+ const convert = conversions(operation.input?.properties, binds, target), scopes = (operation.requires?.auth?.all ?? []).flatMap(p => typeof p === "object" && "ctx.auth.scope" in p ? [p["ctx.auth.scope"]] : []);
48
+ const procedure = kind === "procedures" ? plan.procedures[name] : undefined;
49
+ const output = kind === "procedures" ? ref ? (procedure?.output.type === "object" ? procedure.output : undefined) : { type: "object", properties: { results: { type: "array", items: { type: "array", items: { type: "object" } } } }, required: ["results"] } : undefined;
50
+ const annotations = kind === "views" ? viewAnnotations : procedure ? procedureAnnotations(procedure) : operation.mcp;
51
+ const wire = kind === "views" ? outputConversions(plan, name, target) : [];
52
+ // Hashed as listed: the kernel adds a View tool's paging properties exactly so.
53
+ const listed = versioned({ name: tool, title: operation.title ?? toolTitle(name), description: about, inputSchema: kind === "views" ? { ...inputSchema, properties: { ...inputSchema?.properties, limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: { type: "string" } } } : inputSchema, outputSchema: output, annotations });
54
+ hasWire ||= wire.length > 0;
55
+ const fields = [...(description.targets.find(t => t._meta.id === `${kind === "views" ? "view" : "procedure"}:${name}`)?._meta.readOnly ? ["readOnly: true"] : []), `target: ${q({ kind: kind === "views" ? "view" : "procedure", name })}${guard(operation.requires, `${kind}/${name}`, binds, procedure?.trustedOnly)}`, `name: ${q(tool)}`, `surface: ${q(surface)}`, `title: ${q(listed.title)}`, `description: ${q(listed.description)}`, "inputSchema", ...(output ? [`outputSchema: ${literal(output)}`] : []), ...(annotations ? [`annotations: ${q(annotations)}`] : []), ...((operation.requires?.auth?.all.length ?? 0) > 0 ? ["closed: true"] : []), ...(scopes.length ? [`scopes: ${q(scopes)}`] : []), `validate: ${kind === "views" ? `validate${index}` : `async (raw) => (await import("./validation/procedures/${file}.js")).validate(raw)`}`, ...(convert ? [`convert: ${convert}`] : []),
56
+ ...(kind === "views" ? [`path: ${q(`views/${name}`)}`, `page: ${boundaryPage(`run${index}`, wire)}`] : [`run: ${ref ? `(db, caller, input, handlers) => store(db, caller, { handlers }).procedures[${q(name)}](input)` : `async (db, caller, input) => (await import("./procedures/${file}.js")).default(db, caller, input)`}`])];
57
+ tools.push({ fields: [...fields, `version: ${q(listed.version)}`], schema });
58
+ };
59
+ for (const [name, view] of Object.entries(plan.views))
60
+ if (view.surface !== "internal")
61
+ add(name, "views", view.surface);
62
+ const exposed = new Set();
63
+ for (const trigger of Object.values(plan.triggers))
64
+ if (trigger.source.kind === "mcp") {
65
+ const key = trigger.source.surface + ":" + trigger.procedure;
66
+ if (!exposed.has(key)) {
67
+ exposed.add(key);
68
+ add(trigger.procedure, "procedures", trigger.source.surface);
69
+ }
70
+ }
71
+ for (const schema of Object.values(plan.schemas))
72
+ if (schema.publishing)
73
+ for (const verb of ["publish", "unpublish", "archive"]) {
74
+ const name = `${schema.name}.${verb}`, input = publishingInput;
75
+ Object.assign(files, emitValidation({ views: {}, procedures: { [name]: { input } } }, refuse));
76
+ add(name, "procedures", "admin", { input, title: toolTitle(`${verb}_${schema.name}`), requires: { auth: { all: ["ctx.admin"] } }, mcp: publishingAnnotations(verb) }, mcpToolNameSegment(`${verb}_${schema.name}`));
77
+ }
78
+ if (hasWire)
79
+ imports.push('import { wireRows } from "@aotter/mantle/contracts";');
80
+ // An input schema several tools share is one constant.
81
+ const uses = new Map(), shared = new Map();
82
+ for (const { schema } of tools)
83
+ uses.set(schema, (uses.get(schema) ?? 0) + 1);
84
+ for (const [schema, count] of uses)
85
+ if (count > 1 && schema)
86
+ shared.set(schema, `schema${shared.size}`);
87
+ return { ...files, "mcp.ts": `${[`import { mcp as serve, type Options as McpOptions, type Tool } from "@aotter/mantle/mcp";`, databaseImport(target),
88
+ ...(media ? ['import type { MediaStorage } from "@aotter/mantle/site";'] : []),
89
+ 'import type { Handlers } from "./generated.js";', ...(hasRef ? ['import { store } from "./generated.js";'] : []), ...imports,
90
+ ...[...shared].map(([schema, name]) => `const ${name} = ${schema};`)].join("\n")}
91
+ export type Options<Env = unknown> = McpOptions<Env, Database> & { handlers${refs ? "" : "?"}: Handlers }${media ? " & { media?: (env: Env) => MediaStorage }" : ""};
92
+ /** The generated tools, exported so an app can compose them with its own tools and MCP Apps. */
93
+ export const tools: readonly Tool<Database, Handlers>[] = [
94
+ ${tools.map(({ fields, schema }) => ` { ${fields.map(field => field === "inputSchema" ? `inputSchema: ${shared.get(schema) ?? schema}` : field).join(", ")} },`).join("\n")}
95
+ ];
96
+ export function mcp<Env = unknown>(options: Options<Env>): (request: Request, env: Env, ctx?: import("@aotter/mantle/contracts").RequestContext) => Promise<Response | undefined> {
97
+ return serve<Env, Database, Handlers>(${media ? `async (request, env) => [...tools, ...(options.media && new URL(request.url).pathname.replace(/\\/+$/, "") === "/mcp/admin" ? (await import("./mcp-media.js")).mediaTools<Handlers>(() => options.media!(env)) : [])]` : "tools"}, ${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)}'}, ${media ? `[...${q(reads)}, ...(options.media ? ${q(mediaTools.filter(tool => tool.annotations.readOnlyHint).map(tool => ({ name: tool.name, surface: "admin" })))} : [])]` : q(reads)});
98
+ }
99
+ ` };
100
+ }
@@ -0,0 +1,71 @@
1
+ import { userReferences } from "../domain/userReferences.js";
2
+ /** Selected View boundary: Core prints keyset SQL once; bare operations retain native rows. */
3
+ import { parseNumeric } from "../build/compiler.js";
4
+ import { outputConversions } from "../domain/columns.js";
5
+ import { authLines } from "../domain/input.js";
6
+ import { driverFor, databaseImport, isSqlite, q } from "../domain/entry.js";
7
+ import { recipesOf } from "../domain/recipes.js";
8
+ export function emitPaging(plan, lowered, target, refuse, proofs, boundary = false) {
9
+ const files = {}, base = "@aotter/mantle/";
10
+ const sqlite = isSqlite(target);
11
+ const user = userReferences(plan, sqlite);
12
+ const driver = driverFor(target);
13
+ for (const [name, view] of Object.entries(plan.views)) {
14
+ if (boundary && (view.surface === "internal" || !outputConversions(plan, name, target).some(([, type]) => type === "int")))
15
+ continue;
16
+ const path = `views/${name}`, sel = view.stmts[0]?.SelectStmt;
17
+ const proof = proofs?.[name];
18
+ if (boundary && (!proof || !proof.types.length && !proof.single))
19
+ continue;
20
+ // Internal bare queries remain available even when they cannot be safely paged.
21
+ if (!proof || !proof.types.length && !proof.single) {
22
+ if (view.surface !== "internal")
23
+ refuse(path, proof?.reason ?? "Paged Views need a complete typed NOT NULL key tuple, indexed Schema order and a non-fanning relation, or a proved singleton. For a lookup by a unique field, end ORDER BY with the Schema's id (`ORDER BY t.id`).");
24
+ continue;
25
+ }
26
+ const mode = view.surface === "public" ? "public" : "caller";
27
+ const c = lowered.views[name]?.[mode], trusted = mode === "caller" ? lowered.views[name]?.trusted : undefined;
28
+ if (!c)
29
+ continue;
30
+ const hidden = new Set(Array.from({ length: c.nkeys }, (_, i) => `_k${i}`));
31
+ if (Object.values(c.paged).some(p => p.names.some(name => hidden.has(sqlite ? name.toLowerCase() : name)))) {
32
+ if (view.surface !== "internal")
33
+ refuse(path, "Paged output names must not collide with the compiler's hidden cursor keys.");
34
+ continue;
35
+ }
36
+ const cap = sel.limitCount?.A_Const?.ival?.ival ?? 500;
37
+ if (sel.limitCount && (!sel.limitCount.A_Const?.ival || !Number.isInteger(cap) || cap < 1)) {
38
+ refuse(path, "A paged View LIMIT must be a positive integer literal.");
39
+ continue;
40
+ }
41
+ const values = new Set();
42
+ const kinds = proof.types.map(type => {
43
+ if (type === "date" || type === "timestamptz")
44
+ values.add(type === "date" ? "date" : "timestamp");
45
+ return type === "bool" ? "bool" : type === "int4" ? "int4" : ["integer", "int8"].includes(type) ? "int" : parseNumeric(type) ? "numeric" : ["real", "float8"].includes(type) ? "float" : type === "date" ? "date" : type === "timestamptz" ? "timestamp" : "text";
46
+ });
47
+ const variants = [c, ...(trusted ? [trusted] : [])].map(v => {
48
+ const pages = Object.values(v.paged);
49
+ const first = pages.find(p => !p.sources.some(s => "cursor" in s)), next = pages.find(p => p.sources.some(s => "cursor" in s));
50
+ if (!first || v.nkeys !== proof.types.length || v.nkeys > 0 && !next) {
51
+ refuse(path, "Core could not lower this View's keyset pages.");
52
+ return "";
53
+ }
54
+ const recipes = recipesOf(v, Object.keys(view.input?.properties ?? {}), message => refuse(path, message), values);
55
+ const page = (p) => `{ sql: ${q(p.sql)}, extra: ${q(p.sources.map(s => "cursor" in s ? s.cursor : -1))} }`;
56
+ return `{ binds: ${q(recipes)}, first: ${page(first)}, next: ${page(next ?? first)} }`;
57
+ });
58
+ const auth = authLines(view.requires, path);
59
+ const spec = [`path: ${q(path)}`, `cap: ${cap}`, `sqlite: ${sqlite}`, `cursor: ${q(kinds)}`, `keys: ${c.nkeys}`, `values: {${user.used ? [...values].sort().concat(user.values).join(", ") : values.size ? ` ${[...values].sort().join(", ")} ` : ""}}`, "read", `caller: ${variants[0]}`, ...(trusted ? [`trusted: ${variants[1]}`] : []), ...(auth.length ? [`auth: (caller: Caller) => {\n${auth.join("\n")}\n }`] : [])];
60
+ files[`paged/${boundary ? "wire/" : ""}views/${encodeURIComponent(name)}.ts`] = `${user.code ? user.code + "\n" : ""}import { page, type Paged } from "${base}paging";
61
+ import { ${auth.length ? "deny, isAdmin, isUser, " : ""}type Caller${sqlite ? "" : ", type Unsafe as Database"} } from "${base}contracts";
62
+ ${sqlite ? databaseImport(target) + "\n" : ""}import { read } from "${base}driver/${driver}";
63
+ ${values.size ? `import { ${[...values].sort().join(", ")} } from "${base}dialect/${sqlite ? "sqlite" : "postgres"}";\n` : ""}import type { Input, Row } from "${boundary ? "../../../" : "../../"}views/${encodeURIComponent(name)}.js";
64
+ const spec: Paged<Database> = {
65
+ ${spec.join(",\n ")},
66
+ };
67
+ export default (db: Database, caller: Caller, input: Input, options?: { limit?: number; cursor?: string }) => page<Database, Row>(${user.used ? "{ ...spec, caller: USER.scopedAll(spec.caller, caller) }" : "spec"}, db, caller, input, options);
68
+ `;
69
+ }
70
+ return files;
71
+ }