@aotter/mantle 0.2.1 → 0.2.2-alpha.1

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 (70) hide show
  1. package/dist/auth/routes.d.ts +3 -1
  2. package/dist/auth/routes.js +15 -2
  3. package/dist/build/compiler.d.ts +1 -0
  4. package/dist/build/compiler.js +1 -1
  5. package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
  6. package/dist/bun/assets.d.ts +3 -1
  7. package/dist/bun/assets.js +4 -1
  8. package/dist/domain/describe/chunk-GMUORMOX.js +1 -0
  9. package/dist/domain/describe/index.js +1 -1
  10. package/dist/domain/describe/public.js +1 -1
  11. package/dist/domain/generated-tools.js +24 -9
  12. package/dist/infrastructure/bundle.js +30 -1
  13. package/dist/infrastructure/inputs.js +3 -2
  14. package/dist/infrastructure/wrangler.js +4 -3
  15. package/dist/kernel/authorization.d.ts +1 -0
  16. package/dist/kernel/authorization.js +12 -5
  17. package/dist/kernel/mcp-call.d.ts +3 -3
  18. package/dist/kernel/mcp-call.js +4 -4
  19. package/dist/kernel/mcp-handler.js +4 -3
  20. package/dist/kernel/mcp.d.ts +12 -10
  21. package/dist/kernel/mcp.js +1 -1
  22. package/dist/kernel/media-fetch.js +109 -0
  23. package/dist/kernel/media.d.ts +15 -2
  24. package/dist/kernel/media.js +138 -16
  25. package/dist/kernel/r2-media.d.ts +3 -2
  26. package/dist/kernel/r2-media.js +12 -8
  27. package/dist/kernel/result-meta.d.ts +10 -0
  28. package/dist/kernel/result-meta.js +1 -0
  29. package/dist/kernel/site-types.d.ts +54 -6
  30. package/dist/kernel/site.d.ts +1 -1
  31. package/dist/usecase/generate.js +2 -2
  32. package/dist/usecase/mcp-media.js +17 -5
  33. package/dist/usecase/mcp.js +6 -5
  34. package/dist/usecase/site.js +4 -2
  35. package/docs/examples/README.md +5 -1
  36. package/docs/examples/app-shell/README.md +5 -1
  37. package/docs/examples/app-shell/package.json +9 -9
  38. package/docs/examples/app-shell/src/jwks.ts +22 -0
  39. package/docs/examples/app-shell/src/service.ts +12 -1
  40. package/docs/examples/app-shell/wrangler.jsonc +1 -1
  41. package/docs/examples/club-events/README.md +10 -3
  42. package/docs/examples/club-events/manifests/club-events.yaml +42 -11
  43. package/docs/examples/club-events/smoke.mjs +31 -1
  44. package/docs/examples/club-events/src/handlers.ts +9 -8
  45. package/docs/examples/frontend-mcp-app/README.md +23 -0
  46. package/docs/examples/frontend-mcp-app/app.tsx +50 -0
  47. package/docs/examples/frontend-mcp-app/manifest.yaml +30 -0
  48. package/docs/examples/purchasing/README.md +9 -0
  49. package/docs/examples/purchasing/manifests/purchasing.yaml +18 -16
  50. package/docs/examples/purchasing/smoke.mjs +5 -0
  51. package/docs/examples/purchasing/wrangler.jsonc +1 -1
  52. package/docs/examples/recipes/wrangler.jsonc +1 -1
  53. package/docs/handbook/cloudflare/authentication.md +51 -0
  54. package/docs/handbook/cloudflare/deploy-and-operate.md +5 -0
  55. package/docs/handbook/cloudflare/media-r2.md +5 -2
  56. package/docs/handbook/cloudflare/service-entry.md +28 -7
  57. package/docs/handbook/examples/hub.md +1 -1
  58. package/docs/handbook/guides/mcp-apps.md +94 -18
  59. package/docs/handbook/guides/native-admin.md +29 -0
  60. package/docs/handbook/guides/performance.md +5 -1
  61. package/docs/handbook/guides/revisions.md +1 -1
  62. package/docs/handbook/guides/sql-patterns.md +33 -0
  63. package/docs/handbook/reference/authorization.md +1 -1
  64. package/docs/handbook/reference/describe.md +5 -0
  65. package/docs/handbook/reference/schema.md +19 -0
  66. package/docs/handbook/reference/site-config.md +2 -0
  67. package/docs/handbook/reference/surface.md +66 -10
  68. package/docs/handbook/releases/index.md +24 -1
  69. package/package.json +10 -10
  70. package/dist/domain/describe/chunk-CW7X5Q5T.js +0 -1
@@ -32,6 +32,29 @@ Pin every package you use to the same exact version and upgrade them together.
32
32
  From 0.1.x, follow [Upgrade 0.1 to 0.2](upgrade-0.1-to-0.2.md) instead: a new database, a reviewed and rehearsed data
33
33
  move, and never booting 0.2 against a 0.1.x database.
34
34
 
35
+ ## 0.2.2 — in preparation
36
+
37
+ ### Upgrading to 0.2.2-alpha.1
38
+
39
+ Pin the selected packages to `0.2.2-alpha.1` and regenerate. Nothing breaks, and existing configuration keeps working:
40
+
41
+ - **Media tools.** `/mcp/admin` has four media tools: `upload_media`, `find_media`, `update_media` and
42
+ `delete_media`, plus an upload card. The seven older names still answer, marked "Deprecated, removed in 0.3".
43
+ A media purpose may be `visibility: "private"`: its assets carry no public URL, and `signRead` returns a
44
+ short-lived one. New media ids carry a per-database tag; another server's id is `MEDIA_NOT_OWNED` (404).
45
+ - **MCP.** `discovery: "all"` gives every caller of an endpoint the same `tools/list`, and `authorize` still decides
46
+ each call. The Admin MCP App links at the fixed `ui://mantle-admin/admin` by default; the build's hash URI still
47
+ answers.
48
+ - **Admin sign-in seams.** A `{ kind: "passkey" }` method, a `SignIn` component slot and an `onSignedIn` step; Admin
49
+ ships no passkey experience of its own.
50
+ - **Static files at the edge.** A newly generated `run_worker_first` excludes `/admin/assets/*` and `/assets/*`, and
51
+ `mantle bundle` copies `public/` into the assets directory. Existing `run_worker_first` settings are not changed;
52
+ in a new config an app route under `/assets/` no longer reaches the Worker.
53
+ - **Fixes.** OTP failures are normalised at the HTTP boundary; a Schema check refusal is no longer reported as a stale
54
+ write; media search by id works on D1.
55
+ - **Docs.** [Bulk seeding](../reference/schema.md#seed-in-bulk), changing another member's row with an inline
56
+ `author_id = auth.uid()` ([SQL patterns](../guides/sql-patterns.md)), and a waiting list in the club-events example.
57
+
35
58
  ## 0.2.1 (2026-10-30)
36
59
 
37
60
  0.2.1 adds **Schema locks**: a manifest rule that refuses a row's update or delete on every caller path (REST, MCP,
@@ -494,7 +517,7 @@ See [HTTP, MCP, CLI and packages](../reference/surface.md) for the full list.
494
517
 
495
518
  **Hosting.** `createMantleWorker` assembles the conventional Cloudflare Worker:
496
519
  D1 and assets bindings, Better Auth 1.7 (social providers, email OTP, magic
497
- link, passkey), Admin, MCP, Web and R2 media uploads. The Bun and Vercel
520
+ link, and app-enabled plugins such as passkey), Admin, MCP, Web and R2 media uploads. The Bun and Vercel
498
521
  adapters are experimental, cover public Views and HTTP Triggers only, and leave
499
522
  authentication and CSRF to the host. ChatGPT Sites is a
500
523
  first-class integration with a runnable reference.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.2.1",
3
+ "version": "0.2.2-alpha.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "engines": {
@@ -20,7 +20,7 @@
20
20
  "jsonc-parser": "3.3.1",
21
21
  "@pgsql/types": "18.0.0",
22
22
  "libpg-query": "18.1.5",
23
- "pgsql-deparser": "18.3.10",
23
+ "pgsql-deparser": "18.3.12",
24
24
  "yaml": "^2.9.1",
25
25
  "zod": "^4.6.7"
26
26
  },
@@ -72,9 +72,9 @@
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@modelcontextprotocol/server": "2.3.1",
75
- "@better-auth/core": "1.7.7",
76
- "@better-auth/oauth-provider": "1.7.7",
77
- "better-auth": "1.7.7"
75
+ "@better-auth/core": "1.7.12",
76
+ "@better-auth/oauth-provider": "1.7.12",
77
+ "better-auth": "1.7.12"
78
78
  },
79
79
  "peerDependenciesMeta": {
80
80
  "@modelcontextprotocol/server": {
@@ -93,13 +93,13 @@
93
93
  "devDependencies": {
94
94
  "@modelcontextprotocol/client": "2.3.1",
95
95
  "@modelcontextprotocol/server": "2.3.1",
96
- "@better-auth/core": "1.7.7",
97
- "@better-auth/oauth-provider": "1.7.7",
98
- "@better-auth/test-utils": "1.7.7",
96
+ "@better-auth/core": "1.7.12",
97
+ "@better-auth/oauth-provider": "1.7.12",
98
+ "@better-auth/test-utils": "1.7.12",
99
99
  "@cloudflare/workers-types": "^5.20261027.1",
100
100
  "@types/node": "^26",
101
- "better-auth": "1.7.7",
102
- "bun-types": "1.4.2",
101
+ "better-auth": "1.7.12",
102
+ "bun-types": "1.4.3",
103
103
  "postgres": "3.4.9",
104
104
  "typescript": "^6.0.3",
105
105
  "wrangler": "4.153.0",
@@ -1 +0,0 @@
1
- function canonical(v){return Array.isArray(v)?`[${v.map(canonical).join(",")}]`:v&&typeof v=="object"?`{${Object.keys(v).sort().filter(k=>v[k]!==void 0).map(k=>`${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`:JSON.stringify(v)??"null"}function resolveLocalizedText(value,preferred,canonical2){if(value==null)return null;if(typeof value=="string")return value;if(Object.prototype.hasOwnProperty.call(value,preferred))return value[preferred];if(canonical2&&Object.prototype.hasOwnProperty.call(value,canonical2))return value[canonical2];let firstKey=Object.keys(value)[0];return firstKey!==void 0?value[firstKey]:null}var INPUT_RULE_KEYWORDS=["dependentRequired","anyOf","if","then","else"];function lookupLocalRef(root,ref){if(typeof ref!="string"||!ref.startsWith("#/"))return;let value=root;try{for(let key of decodeURIComponent(ref.slice(2)).split("/").map(key2=>key2.replace(/~1/g,"/").replace(/~0/g,"~")))value=value&&typeof value=="object"&&Object.hasOwn(value,key)?value[key]:void 0}catch{return}return value&&typeof value=="object"?value:void 0}function rootRefChain(input){let chain=[];for(let schema=input;schema!==void 0;schema=lookupLocalRef(input,schema.$ref)){if(chain.includes(schema))return;if(chain.push(schema),schema.$ref===void 0)return chain}}function mcpToolNameSegment(name){return name.toLowerCase().replace(/-/g,"_")}function toolTitle(name){let words=name.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/[-_.\s]+/g," ").trim().toLowerCase();return words.charAt(0).toUpperCase()+words.slice(1)}var viewAnnotations={readOnlyHint:!0,destructiveHint:!1};function procedureAnnotations(p){let readOnlyHint=p.mcp?.readOnlyHint??!1;return{readOnlyHint,destructiveHint:!readOnlyHint&&("ref"in p.handler||overwrites(p.handler.sql.stmts)),...p.mcp}}var overwrites=node=>!!node&&typeof node=="object"&&("DeleteStmt"in node||"UpdateStmt"in node||node.onConflictClause?.action==="ONCONFLICT_UPDATE"||Object.values(node).some(overwrites));function mcpTools(plan,surface,locale="en"){let text=t=>resolveLocalizedText(t,locale,"en")??void 0,tools=new Map,add=tool=>{let prior=tools.get(tool.name);if(prior&&(prior.kind!==tool.kind||prior.source!==tool.source))throw new TypeError(`MCP tool '${tool.name}' comes from both '${prior.source}' and '${tool.source}'.`);tools.set(tool.name,tool)};for(let t of Object.values(plan.triggers)){if(t.source.kind!=="mcp"||t.source.surface!==surface)continue;let p=plan.procedures[t.procedure],title=text(p.title);add({name:mcpToolNameSegment(t.procedure),kind:"procedure",source:t.procedure,title:title??toolTitle(t.procedure),description:text(p.description)??title??t.procedure,inputSchema:p.input,...p.output.type==="object"?{outputSchema:p.output}:{},annotations:procedureAnnotations(p),requires:p.requires})}for(let[name,v]of Object.entries(plan.views)){if(v.surface!==surface)continue;let chain=(v.input?rootRefChain(v.input)??[v.input]:[]).reverse(),props=Object.assign({},...chain.map(s=>s.properties??{})),required=[...new Set(chain.flatMap(s=>s.required??[]))];if("limit"in props||"cursor"in props)throw new TypeError(`View '${name}' declares an input named limit or cursor; the MCP tool reserves both for paging.`);let rules=chain.map(s=>Object.fromEntries(INPUT_RULE_KEYWORDS.flatMap(k=>s[k]===void 0?[]:[[k,s[k]]]))).filter(r=>Object.keys(r).length),defs=v.input?referencedDefs(v.input,props):{},title=text(v.title);add({name:mcpToolNameSegment(name),kind:"view",source:name,title:title??toolTitle(name),description:text(v.description)??title??name,inputSchema:{type:"object",properties:{...props,limit:{type:"integer",minimum:1,maximum:500},cursor:{type:"string"}},...required.length?{required}:{},...rules.length>1?{allOf:rules}:rules[0],...Object.keys(defs).length?{$defs:defs}:{}},annotations:viewAnnotations,requires:v.requires})}return[...tools.values()]}function referencedDefs(root,node,found={}){if(!node||typeof node!="object")return found;let ref=node.$ref,[head,segment]=typeof ref=="string"&&ref.startsWith("#/")?decoded(ref.slice(2)).split("/"):[],key=head==="$defs"?segment?.replace(/~1/g,"/").replace(/~0/g,"~"):void 0;key!==void 0&&!Object.hasOwn(found,key)&&root.$defs&&Object.hasOwn(root.$defs,key)&&(found[key]=root.$defs[key],referencedDefs(root,found[key],found));for(let child of Object.values(node))referencedDefs(root,child,found);return found}var decoded=pointer=>{try{return decodeURIComponent(pointer)}catch{return""}};var publishingTransitions=[["publish",["draft"],"published"],["unpublish",["published","archived"],"draft"],["archive",["draft","published"],"archived"]];var publishingInput={type:"object",properties:{id:{type:"string",minLength:1},expectedVersion:{type:"integer",minimum:1}},required:["id","expectedVersion"],additionalProperties:!1};function mediaDefinitions(){let define=(name,description,inputSchema,action,readOnlyHint=!1)=>({name,description,inputSchema,action,annotations:{readOnlyHint,destructiveHint:action==="delete",openWorldHint:!0}}),string={type:"string"},texts={alt:{type:"string",maxLength:1e3},caption:{type:"string",maxLength:1e3}},input=(properties,required=[])=>({type:"object",properties,required,additionalProperties:!1});return[define("get_media_upload_policy","Read declared image purpose MIME slots and byte caps before preparing variants or requesting an upload. Optimization runs locally, never in the host.",input({}),"policy",!0),define("create_media_upload","Create short-lived PUT capabilities for image variants. Read site media purpose policy first; optimize images locally. Obtain attachment bytes yourself, then PUT each variant with its requiredHeaders and call commit_media_upload. Never store upload URLs as published images or ask the user to run terminal commands.",input({filename:{type:"string",maxLength:255},purpose:string,variants:{type:"array",minItems:1,items:input({mimeType:string,byteSize:{type:"integer",minimum:1},role:{type:"string",enum:["primary","alternate","fallback"]}},["mimeType","byteSize","role"])},...texts},["filename","purpose","variants"]),"create"),define("commit_media_upload","Verify all uploaded variants and commit one permanent media asset. Store the returned asset id in fields referencing media_assets.id, never a temporary upload URL. Unknown or expired upload groups require a new create_media_upload.",input({uploadGroupId:string,...texts},["uploadGroupId"]),"commit"),define("list_media_assets","List committed image assets, newest first, with their permanent variants and alt/caption. Search matches id, alt or caption; reuse only a returned cursor.",input({limit:{type:"integer",minimum:1,maximum:500},cursor:string,search:string}),"list",!0),define("get_media_asset","Read one committed media asset and its permanent variant URLs.",input({id:string},["id"]),"get",!0),define("update_media_asset","Update committed asset alt or caption; omitted fields remain unchanged and empty strings clear text.",input({id:string,...texts},["id"]),"update"),define("delete_media_asset","Delete an asset's objects and library record. Check application references first. A partial failure can be retried using the same id.",input({id:string},["id"]),"delete")]}var ADMIN_API_BASE="/admin/api",pathTemplate=path=>path.replace(/\{([^}]+)\}/g,":$1"),routePath=template=>template.split("/").map(segment=>segment.startsWith(":")?`{${segment.slice(1)}}`:segment).join("/");function routeSource(template,base="",suffix=!1){let keys=[],path=template.split("/").map(segment=>{let rest=suffix?/^\{([a-zA-Z]\w*)\*\}$/.exec(segment):null,one=/^\{([^}]+)\}$/.exec(segment);return rest||one?(keys.push((rest??one)[1]),rest?"(.*)":"([^/]+)"):encodeURIComponent(segment).replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("/");return{source:`^${base}${path}/?$`,keys}}function canonicalPath(pathname){try{return pathname.split("/").map(segment=>encodeURIComponent(decodeURIComponent(segment))).join("/")}catch{return}}var isParameter=segment=>/^\{[^}]+\}$/.test(segment);function routeOrder(a,b){let x=a.split("/"),y=b.split("/"),count=segments=>segments.filter(isParameter).length;if(count(x)!==count(y))return count(x)-count(y);for(let i=0;i<Math.min(x.length,y.length);i++)if(isParameter(x[i])!==isParameter(y[i]))return isParameter(x[i])?1:-1;return a<b?-1:a>b?1:0}function inspectSql(value){let schemas=new Set,readSchemas=new Set,writes=[],readOnly=!0,walk=v=>{if(!v||typeof v!="object")return;let n=v;n.RangeVar?.mantle==="table"&&(schemas.add(n.RangeVar.relname.toLowerCase()),readSchemas.add(n.RangeVar.relname.toLowerCase()));for(let kind of["InsertStmt","UpdateStmt","DeleteStmt"])n[kind]&&(readOnly=!1,n[kind].relation?.relname&&writes.push({schema:n[kind].relation.relname.toLowerCase(),operation:kind==="InsertStmt"?"create":kind==="UpdateStmt"?"update":"delete"}),n[kind].relation?.relname&&(schemas.add(n[kind].relation.relname.toLowerCase()),kind!=="InsertStmt"&&readSchemas.add(n[kind].relation.relname.toLowerCase())));Object.values(n).forEach(walk)};return walk(value),{schemas:[...schemas].sort(),readSchemas:[...readSchemas].sort(),readOnly,writes}}function mergeRestrict(policies,conjunctive){return Object.fromEntries([...new Set(policies.flatMap(p=>Object.keys(p)))].sort().map(schema=>{let rows=policies.map(p=>p[schema]??{terms:[]}),terms=[...new Map(rows.flatMap(p=>p.terms).map(t=>[canonical(t),t])).values()].filter(t=>conjunctive||rows.every(p=>p.terms.some(v=>canonical(v)===canonical(t)))),owner=rows.find(p=>p.owner)?.owner;return[schema,{...owner&&(conjunctive||rows.every(p=>p.owner===owner))?{owner}:{},terms}]}))}function inspectBehavior(plan,ir,opaque,requires,restrict,writes){let inspected=inspectSql(ir);writes&&(inspected.writes=[...writes],inspected.schemas.push(...writes.map(w=>w.schema)),inspected.readSchemas.push(...writes.filter(w=>w.operation!=="create").map(w=>w.schema)));let dependencies=[],policies=[restrict??{}],procedures=new Set,hooks=new Set,visit=name=>{if(procedures.has(name))return;procedures.add(name);let p=plan.procedures[name];if(!p)throw new TypeError(`Unknown dependency '${name}'.`);dependencies.push({procedure:name,declaration:p}),policies.push(p.restrict??{}),opaque||="ref"in p.handler;let g=inspectSql("sql"in p.handler?p.handler.sql.stmts:void 0);inspected.schemas.push(...g.schemas),inspected.readSchemas.push(...g.readSchemas),inspected.writes.push(...g.writes),inspected.readOnly&&=g.readOnly,p.requires?.guard&&visit(p.requires.guard.procedure)};for(requires?.guard&&visit(requires.guard.procedure);;){let before=hooks.size;for(let[id,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0)){if(t.source.kind!=="lifecycle"||hooks.has(id))continue;let source=t.source;!opaque&&!inspected.writes.some(w=>w.schema===source.schema.toLowerCase()&&source.on.some(on=>on.endsWith(`_${w.operation}`)))||(hooks.add(id),dependencies.push({trigger:id,source}),visit(t.procedure))}if(hooks.size===before)break}let related=names=>{let all=new Set(opaque?Object.keys(plan.schemas):names);for(let name of all){let parent=plan.schemas[name]?.translates?.parent.toLowerCase();parent&&all.add(parent)}return[...all].sort()};return{...inspected,schemas:related(inspected.schemas),readSchemas:related(inspected.readSchemas),opaque,restrict:mergeRestrict(policies,!0),dependencies}}var objectInput={type:"object",properties:{}},adminRoutes=["GET /me","GET /site","GET /collections","GET /views-manifest","GET /operations","GET /webmcp","POST /webmcp/:tool","GET /bootstrap","GET /views/:name/export","GET /views/:name","GET /view-batch","GET /developer/views/:name","POST /operations/:name","GET /developer-console","GET /collections/:name/statistics","GET /entries","GET /entries/export","GET /entries/:id","POST /entries","PATCH /entries/:id","POST /entries/:id/publish","POST /entries/:id/unpublish","POST /entries/:id/archive","DELETE /entries/:id","GET /site-settings","PATCH /site-settings","POST /media/uploads","POST /media/uploads/:groupId/commit","GET /media","GET /media/:id","PATCH /media/:id","DELETE /media/:id","GET /admin","GET /members","PATCH /admin/:id/role","POST /admin/invitations","DELETE /admin/invitations/:id"];function targetDefinitions(plan){let out=[],tools=[...mcpTools(plan,"public"),...mcpTools(plan,"admin")];for(let[kind,entries]of[["view",plan.views],["procedure",plan.procedures]])for(let[name,p]of Object.entries(entries).sort(([a],[b])=>a<b?-1:a>b?1:0)){let view=kind==="view"?plan.views[name]:void 0,proc=kind==="procedure"?plan.procedures[name]:void 0,ir=view?.stmts??(proc&&"sql"in proc.handler?proc.handler.sql.stmts:void 0),inspected=inspectBehavior(plan,ir,!!proc&&"ref"in proc.handler,p.requires,p.restrict),tool=tools.find(t=>t.kind===kind&&t.source===name),title=resolveLocalizedText(p.title,"en","en")??name,triggers=Object.entries(plan.triggers).filter(([,t])=>kind==="procedure"&&t.procedure===name).sort(([a],[b])=>a<b?-1:a>b?1:0).map(([id,t])=>({id,source:t.source})),surfaces=[...new Set([...view?[view.surface,"admin",...view.surface==="public"?["rest"]:[]]:[],...triggers.map(t=>t.source.kind==="mcp"?t.source.surface:t.source.kind==="http"?"rest":t.source.kind)])].sort();surfaces.length||surfaces.push("internal"),out.push({id:`${kind}:${name}`,name:tool?.name??mcpToolNameSegment(name),kind,surface:view?.surface??(surfaces.includes("public")||surfaces.includes("rest")?"public":surfaces.includes("admin")?"admin":"internal"),surfaces,triggers,title,description:resolveLocalizedText(p.description,"en","en")??title,inputSchema:tool?.inputSchema??view?.input??proc?.input??objectInput,annotations:tool?.annotations??{},requires:p.requires,restrict:inspected.restrict,schemas:inspected.schemas,readSchemas:inspected.readSchemas,readOnly:!inspected.opaque&&inspected.readOnly,opaque:inspected.opaque,trustedOnly:!!proc?.trustedOnly,variants:[],behavior:{declaration:p,dependencies:inspected.dependencies}})}let addRoute=(id,dependencies,inputSchema=objectInput)=>{let admin=id.startsWith("admin:");out.push({id,name:id.replace(/[^a-zA-Z0-9_-]/g,"_"),title:id,description:id,kind:admin?"admin":"rest",surface:admin?"admin":"public",surfaces:[admin?"admin":"rest"],triggers:dependencies.flatMap(d=>d.triggers),inputSchema,annotations:{},requires:admin?{auth:{all:["ctx.admin"]}}:dependencies[0]?.requires,restrict:mergeRestrict(dependencies.map(d=>d.restrict),!1),schemas:[...new Set(dependencies.flatMap(d=>d.schemas))].sort(),readSchemas:[...new Set(dependencies.flatMap(d=>d.readSchemas))].sort(),readOnly:dependencies.length>0&&dependencies.every(d=>d.readOnly)&&(!admin||id.startsWith("admin:GET ")||id==="admin:POST /operations/:name"||id==="admin:POST /webmcp/:tool"),opaque:dependencies.some(d=>d.opaque),trustedOnly:dependencies.some(d=>d.trustedOnly),variants:dependencies,behavior:dependencies.map(d=>({id:d.id,behavior:d.behavior,surfaces:d.surfaces,triggers:d.triggers,input:d.inputSchema,requires:d.requires,restrict:d.restrict,trustedOnly:d.trustedOnly}))})},declarations=[...out],adminOperations=new Set(mcpTools(plan,"admin").filter(t=>t.kind==="procedure").map(t=>`procedure:${t.source}`));for(let d of declarations)d.kind==="view"&&d.surfaces.includes("public")&&addRoute(`rest:GET /api/views/${d.id.slice(5)}`,[d],d.inputSchema);for(let[,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0))if(t.source.kind==="http"){let d=declarations.find(d2=>d2.id===`procedure:${t.procedure}`);addRoute(`rest:${t.source.method} ${pathTemplate(t.source.path)}`,[d],d.inputSchema)}let generated=[];for(let[schema,s]of Object.entries(plan.schemas).sort(([a],[b])=>a<b?-1:a>b?1:0))if(s.publishing)for(let[verb,from,to]of publishingTransitions){addRoute(`procedure:${s.name}.${verb}`,[],publishingInput);let d=out.at(-1),inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,[{schema,operation:verb==="publish"?"publish":"update"}]);Object.assign(d,{kind:"procedure",name:mcpToolNameSegment(`${verb}_${s.name}`),surface:"admin",surfaces:["admin"],requires:{auth:{all:["ctx.admin"]}},schemas:[...new Set([schema,...inspected.schemas])].sort(),readSchemas:[...new Set([schema,...inspected.readSchemas])].sort(),opaque:inspected.opaque,behavior:{schema,verb,from,to,dependencies:inspected.dependencies}}),generated.push(d)}for(let tool of mediaDefinitions()){addRoute(`admin:tool:${tool.name}`,[],tool.inputSchema);let d=out.at(-1);Object.assign(d,{name:tool.name,readOnly:tool.annotations.readOnlyHint,opaque:!tool.annotations.readOnlyHint,annotations:tool.annotations,behavior:{tool,schemas:plan.schemas}}),generated.push(d)}for(let route2 of adminRoutes){let deps=route2.includes("/views/:")||route2==="GET /view-batch"?declarations.filter(d2=>d2.kind==="view"&&(route2.includes("/developer/")||plan.views[d2.id.slice(5)]?.surface==="admin")):route2.includes("/operations/:")?declarations.filter(d2=>adminOperations.has(d2.id)):route2==="POST /webmcp/:tool"?declarations.filter(d2=>mcpTools(plan,"admin").some(t=>`${t.kind}:${t.source}`===d2.id)).concat(generated):[],generic=route2.includes("/entries")||route2.includes("/statistics")||route2==="GET /bootstrap";addRoute(`admin:${route2}`,deps,deps.length&&route2!=="GET /view-batch"?{...objectInput,anyOf:deps.map(d2=>({type:"object",properties:{[route2.includes("/webmcp/")?"tool":"name"]:{type:"string",const:route2.includes("/webmcp/")?d2.name:d2.id.slice(d2.kind==="view"?5:10)}}}))}:objectInput);let d=out.at(-1);if(generic){let operation=route2==="POST /entries"?"create":route2==="DELETE /entries/:id"?"delete":route2.startsWith("GET ")?void 0:route2.endsWith("/publish")?"publish":"update",inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,operation?Object.keys(plan.schemas).map(schema=>({schema,operation})):[]);d.schemas=Object.keys(plan.schemas).sort(),d.readSchemas=d.schemas,d.opaque=inspected.opaque,d.behavior={schemas:plan.schemas,dependencies:inspected.dependencies}}}return[...new Map(out.map(d=>[d.id,d])).values()].sort((a,b)=>a.id<b.id?-1:a.id>b.id?1:0)}var tables=new WeakMap,route=/^(rest|admin):([A-Z]+) (\/.*)$/,normalized=new Set(["DELETE","GET","HEAD","OPTIONS","POST","PUT"]);function compile(describe){let table={rest:[],admin:[],tools:[]};for(let{name,_meta:meta}of describe.targets){let[,surface,method,template]=route.exec(meta.id)??[];if(surface==="rest")table.rest.push({method,pattern:new RegExp(routeSource(routePath(template)).source),view:meta.variants[0]?.id.startsWith("view:")===!0,meta});else if(surface==="admin")table.admin.push({method,pattern:new RegExp(routeSource(routePath(template),ADMIN_API_BASE).source),meta});else{let s=meta.surfaces,endpoints=meta.id.startsWith("admin:tool:")?["admin"]:meta.id.startsWith("view:")?s.includes("public")?["public"]:s.includes("internal")?[]:["admin"]:meta.id.startsWith("procedure:")?s.filter(x=>x==="public"||x==="admin"):[];endpoints.length&&table.tools.push({name,endpoints,meta})}}let order=id=>adminRoutes.indexOf(id.slice(6))>>>0,path=id=>routePath(id.slice(id.indexOf(" ")+1));return table.rest.sort((a,b)=>routeOrder(path(a.meta.id),path(b.meta.id))),table.admin.sort((a,b)=>order(a.meta.id)-order(b.meta.id)),table}function matchTarget(describe,request){let table=tables.get(describe);if(table||tables.set(describe,table=compile(describe)),request.surface==="mcp"){let path2=request.path?.replace(/\/+$/,""),endpoint=path2===void 0?void 0:path2==="/mcp"?"public":path2==="/mcp/admin"?"admin":null,found=table.tools.filter(t=>t.name===request.tool&&(endpoint===void 0||t.endpoints.includes(endpoint)));return endpoint!==null&&found.length&&found.every(t=>t.meta.id===found[0].meta.id)?found[0].meta:null}let method=normalized.has(request.method.toUpperCase())?request.method.toUpperCase():request.method,path=canonicalPath(request.path);return path===void 0?null:request.surface==="admin"?request.path!==ADMIN_API_BASE&&!request.path.startsWith(ADMIN_API_BASE+"/")?null:table.admin.find(r=>r.method===method&&r.pattern.test(path))?.meta??null:table.rest.find(r=>r.pattern.test(path)&&(r.method===method||method==="HEAD"&&r.method==="GET"&&r.view))?.meta??null}export{canonical,targetDefinitions,matchTarget};