theokit 0.72.1 → 0.74.0

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 (143) hide show
  1. package/README.md +2 -0
  2. package/dist/{actions-virtual-module-OHENNMQG.js → actions-virtual-module-J2HYO7QZ.js} +4 -4
  3. package/dist/{actions-virtual-module-EAKYEMGF.js → actions-virtual-module-JV7URROJ.js} +7 -7
  4. package/dist/adapters/agent-mount.js +3 -3
  5. package/dist/adapters/security-headers.d.ts +27 -11
  6. package/dist/adapters/security-headers.js +9 -4
  7. package/dist/adapters/security-headers.js.map +1 -1
  8. package/dist/{agent-VJTZ4DQ7.js → agent-GU6JIEFJ.js} +3 -3
  9. package/dist/{app-typed-client-ILLQYTXP.js → app-typed-client-EOA3A2AR.js} +5 -5
  10. package/dist/{app-typed-client-ILLQYTXP.js.map → app-typed-client-EOA3A2AR.js.map} +1 -1
  11. package/dist/{app-typed-client-LHH77QDB.js → app-typed-client-XD4DZBV7.js} +8 -8
  12. package/dist/app-typed-client-XD4DZBV7.js.map +1 -0
  13. package/dist/{aws-lambda-7AZNJGWU.js → aws-lambda-ZYSPTD3O.js} +61 -27
  14. package/dist/aws-lambda-ZYSPTD3O.js.map +1 -0
  15. package/dist/{build-23QF5FNQ.js → build-6FVZKORI.js} +81 -74
  16. package/dist/build-6FVZKORI.js.map +1 -0
  17. package/dist/{bun-NKVHOFSJ.js → bun-I7YM3RDB.js} +82 -21
  18. package/dist/bun-I7YM3RDB.js.map +1 -0
  19. package/dist/{chunk-WBB3MEEN.js → chunk-2NG5T77O.js} +47 -12
  20. package/dist/{chunk-WBB3MEEN.js.map → chunk-2NG5T77O.js.map} +1 -1
  21. package/dist/{chunk-NO6HA4FG.js → chunk-4WP7A7RX.js} +41 -35
  22. package/dist/chunk-4WP7A7RX.js.map +1 -0
  23. package/dist/chunk-4X43VTST.js +31 -0
  24. package/dist/chunk-4X43VTST.js.map +1 -0
  25. package/dist/{chunk-FHQP23Z5.js → chunk-6R5F5YF2.js} +17 -14
  26. package/dist/chunk-6R5F5YF2.js.map +1 -0
  27. package/dist/chunk-72RSCKMW.js +53 -0
  28. package/dist/chunk-72RSCKMW.js.map +1 -0
  29. package/dist/{chunk-V5SSTT3C.js → chunk-C43AHOHI.js} +1 -1
  30. package/dist/chunk-C43AHOHI.js.map +1 -0
  31. package/dist/{chunk-C5CAL3DX.js → chunk-EQJF2EH5.js} +8 -7
  32. package/dist/chunk-EQJF2EH5.js.map +1 -0
  33. package/dist/{chunk-QD5X56I3.js → chunk-FNJVSFEC.js} +1 -1
  34. package/dist/chunk-FNJVSFEC.js.map +1 -0
  35. package/dist/{chunk-6U67SO5U.js → chunk-HK57MPSZ.js} +2 -2
  36. package/dist/{chunk-R7G2B2WH.js → chunk-HKF6YNY4.js} +17 -14
  37. package/dist/{chunk-R7G2B2WH.js.map → chunk-HKF6YNY4.js.map} +1 -1
  38. package/dist/chunk-IJQQNHL7.js +34 -0
  39. package/dist/chunk-IJQQNHL7.js.map +1 -0
  40. package/dist/{chunk-RE2WE7CD.js → chunk-LOOOB46A.js} +2 -2
  41. package/dist/{chunk-PEL3MJLM.js → chunk-MX63AWEK.js} +1 -1
  42. package/dist/{chunk-PEL3MJLM.js.map → chunk-MX63AWEK.js.map} +1 -1
  43. package/dist/chunk-NPQ5A3RU.js +53 -0
  44. package/dist/chunk-NPQ5A3RU.js.map +1 -0
  45. package/dist/{chunk-DKX7LF5Q.js → chunk-ODYLKWRT.js} +3 -3
  46. package/dist/{chunk-L6UHMWXU.js → chunk-QAS5SKZP.js} +3 -3
  47. package/dist/chunk-QAS5SKZP.js.map +1 -0
  48. package/dist/{chunk-4EQEJ4PH.js → chunk-RFZ263XP.js} +68 -23
  49. package/dist/chunk-RFZ263XP.js.map +1 -0
  50. package/dist/{chunk-XLSJWKSP.js → chunk-S5QN3O22.js} +66 -21
  51. package/dist/{chunk-XLSJWKSP.js.map → chunk-S5QN3O22.js.map} +1 -1
  52. package/dist/{chunk-E2RC367T.js → chunk-SHIXTMQE.js} +49 -14
  53. package/dist/{chunk-E2RC367T.js.map → chunk-SHIXTMQE.js.map} +1 -1
  54. package/dist/{chunk-G7U7FQVC.js → chunk-UFBBFP75.js} +13 -3
  55. package/dist/chunk-UFBBFP75.js.map +1 -0
  56. package/dist/{chunk-4CIHLTSI.js → chunk-V7ISZGNS.js} +45 -39
  57. package/dist/chunk-V7ISZGNS.js.map +1 -0
  58. package/dist/{chunk-RCFD5W5V.js → chunk-WR5B2XNT.js} +6 -6
  59. package/dist/chunk-WR5B2XNT.js.map +1 -0
  60. package/dist/{chunk-IB5VIXLR.js → chunk-XLFDYTWO.js} +2 -2
  61. package/dist/cli/index.js +8 -8
  62. package/dist/client/index.d.ts +18 -1
  63. package/dist/client/index.js +15 -1
  64. package/dist/client/index.js.map +1 -1
  65. package/dist/{cloudflare-4M4XVNUH.js → cloudflare-AMH4TJNL.js} +104 -82
  66. package/dist/cloudflare-AMH4TJNL.js.map +1 -0
  67. package/dist/{config-support-SB5NDQZB.js → config-support-MOZYHRWG.js} +13 -2
  68. package/dist/{config-support-SB5NDQZB.js.map → config-support-MOZYHRWG.js.map} +1 -1
  69. package/dist/{deno-deploy-IVACZ62D.js → deno-deploy-6H354VRA.js} +17 -7
  70. package/dist/deno-deploy-6H354VRA.js.map +1 -0
  71. package/dist/{dev-JDJWMRLH.js → dev-HJUD6X3R.js} +9 -9
  72. package/dist/{dev-emit-7KZFUA7M.js → dev-emit-T3X6RR7M.js} +5 -5
  73. package/dist/{dev-emit-7KZFUA7M.js.map → dev-emit-T3X6RR7M.js.map} +1 -1
  74. package/dist/{dev-emit-JPJJFBXL.js → dev-emit-ZK5GCHXN.js} +6 -6
  75. package/dist/dev-emit-ZK5GCHXN.js.map +1 -0
  76. package/dist/index.js +5 -5
  77. package/dist/{internal-api-VDYOXOT7.js → internal-api-GKPJYL3G.js} +7 -7
  78. package/dist/{internal-api-6XNLCNI5.js → internal-api-ZUBUYGCQ.js} +4 -4
  79. package/dist/{mcp-WJNCTM7M.js → mcp-MVKDWZEQ.js} +3 -3
  80. package/dist/{netlify-3HEWV375.js → netlify-ALPCTQFO.js} +158 -48
  81. package/dist/netlify-ALPCTQFO.js.map +1 -0
  82. package/dist/{node-QUGTW6XV.js → node-4HQO6KKZ.js} +2 -2
  83. package/dist/{observability-bootstrap-FP6LMMJE.js → observability-bootstrap-DOWRMADT.js} +2 -2
  84. package/dist/{openapi-Q53YOBIU.js → openapi-C4UX2SFV.js} +5 -5
  85. package/dist/openapi-C4UX2SFV.js.map +1 -0
  86. package/dist/{preview-BARW75PN.js → preview-5HUID27O.js} +3 -3
  87. package/dist/registry-OMLCP4AS.js +34 -0
  88. package/dist/registry-OMLCP4AS.js.map +1 -0
  89. package/dist/{routes-FDF4SLOB.js → routes-3PTNISSO.js} +2 -2
  90. package/dist/server/index.js +2 -2
  91. package/dist/server/scan/index.d.ts +38 -2
  92. package/dist/server/scan/index.js +2 -2
  93. package/dist/{server-boundary-AO6EELRF.js → server-boundary-QCJNECJP.js} +7 -7
  94. package/dist/{server-boundary-E5GFVPLB.js → server-boundary-RTRPHX5I.js} +4 -4
  95. package/dist/{start-4JTYXQQJ.js → start-EREUCSN6.js} +10 -10
  96. package/dist/{static-NQPLPAB6.js → static-7LP7LPGN.js} +2 -2
  97. package/dist/{vercel-5PMYEEKF.js → vercel-VLR52RXT.js} +173 -73
  98. package/dist/vercel-VLR52RXT.js.map +1 -0
  99. package/dist/vite-plugin/index.js +5 -5
  100. package/dist/{vite-plugin-3ON45MQE.js → vite-plugin-JVGI6QUO.js} +9 -9
  101. package/package.json +4 -4
  102. package/dist/app-typed-client-LHH77QDB.js.map +0 -1
  103. package/dist/aws-lambda-7AZNJGWU.js.map +0 -1
  104. package/dist/build-23QF5FNQ.js.map +0 -1
  105. package/dist/bun-NKVHOFSJ.js.map +0 -1
  106. package/dist/chunk-4CIHLTSI.js.map +0 -1
  107. package/dist/chunk-4EQEJ4PH.js.map +0 -1
  108. package/dist/chunk-C5CAL3DX.js.map +0 -1
  109. package/dist/chunk-FHQP23Z5.js.map +0 -1
  110. package/dist/chunk-G7U7FQVC.js.map +0 -1
  111. package/dist/chunk-L6UHMWXU.js.map +0 -1
  112. package/dist/chunk-NO6HA4FG.js.map +0 -1
  113. package/dist/chunk-QD5X56I3.js.map +0 -1
  114. package/dist/chunk-RCFD5W5V.js.map +0 -1
  115. package/dist/chunk-V5SSTT3C.js.map +0 -1
  116. package/dist/cloudflare-4M4XVNUH.js.map +0 -1
  117. package/dist/deno-deploy-IVACZ62D.js.map +0 -1
  118. package/dist/dev-emit-JPJJFBXL.js.map +0 -1
  119. package/dist/netlify-3HEWV375.js.map +0 -1
  120. package/dist/openapi-Q53YOBIU.js.map +0 -1
  121. package/dist/registry-SDBODRU6.js +0 -23
  122. package/dist/registry-SDBODRU6.js.map +0 -1
  123. package/dist/vercel-5PMYEEKF.js.map +0 -1
  124. /package/dist/{actions-virtual-module-OHENNMQG.js.map → actions-virtual-module-J2HYO7QZ.js.map} +0 -0
  125. /package/dist/{actions-virtual-module-EAKYEMGF.js.map → actions-virtual-module-JV7URROJ.js.map} +0 -0
  126. /package/dist/{agent-VJTZ4DQ7.js.map → agent-GU6JIEFJ.js.map} +0 -0
  127. /package/dist/{chunk-6U67SO5U.js.map → chunk-HK57MPSZ.js.map} +0 -0
  128. /package/dist/{chunk-RE2WE7CD.js.map → chunk-LOOOB46A.js.map} +0 -0
  129. /package/dist/{chunk-DKX7LF5Q.js.map → chunk-ODYLKWRT.js.map} +0 -0
  130. /package/dist/{chunk-IB5VIXLR.js.map → chunk-XLFDYTWO.js.map} +0 -0
  131. /package/dist/{dev-JDJWMRLH.js.map → dev-HJUD6X3R.js.map} +0 -0
  132. /package/dist/{internal-api-VDYOXOT7.js.map → internal-api-GKPJYL3G.js.map} +0 -0
  133. /package/dist/{internal-api-6XNLCNI5.js.map → internal-api-ZUBUYGCQ.js.map} +0 -0
  134. /package/dist/{mcp-WJNCTM7M.js.map → mcp-MVKDWZEQ.js.map} +0 -0
  135. /package/dist/{node-QUGTW6XV.js.map → node-4HQO6KKZ.js.map} +0 -0
  136. /package/dist/{observability-bootstrap-FP6LMMJE.js.map → observability-bootstrap-DOWRMADT.js.map} +0 -0
  137. /package/dist/{preview-BARW75PN.js.map → preview-5HUID27O.js.map} +0 -0
  138. /package/dist/{routes-FDF4SLOB.js.map → routes-3PTNISSO.js.map} +0 -0
  139. /package/dist/{server-boundary-AO6EELRF.js.map → server-boundary-QCJNECJP.js.map} +0 -0
  140. /package/dist/{server-boundary-E5GFVPLB.js.map → server-boundary-RTRPHX5I.js.map} +0 -0
  141. /package/dist/{start-4JTYXQQJ.js.map → start-EREUCSN6.js.map} +0 -0
  142. /package/dist/{static-NQPLPAB6.js.map → static-7LP7LPGN.js.map} +0 -0
  143. /package/dist/{vite-plugin-3ON45MQE.js.map → vite-plugin-JVGI6QUO.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/action-scan.ts","../src/server/scan/match.ts","../src/core/contracts/http-methods.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/detect-route-policy.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/actions/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nexport interface ActionNode {\n filePath: string\n actionPath: string\n}\n\n/**\n * Directory under `<serverDir>/actions/` reserved for ISOMORPHIC zod schemas.\n *\n * The one place in the whole server tree whose files are meant to reach the browser: the\n * `@theo/actions` virtual module emits `import { schema } from '<abs path>'` into the CLIENT\n * facade for each file here, so `<TheoForm>` can drive `zodResolver` with the same schema the\n * server validates against.\n *\n * Named once, and exported, because two rules now depend on knowing it. The scanner skips it (a\n * schema is not an executable action), and the server-only import boundary must NOT refuse it\n * (`vite-plugin/server-boundary.ts`). Restating the literal in the boundary would let the two\n * drift, and the drift would be invisible: renaming the convention here would silently turn the\n * exception into a refusal, breaking a shipped feature at build time in consumer projects only.\n */\nexport const ACTION_SCHEMAS_DIR = 'schemas'\n\n/**\n * Enriched manifest entry per plan g3-server-actions-and-useaction v1.2\n * § Phase 1 / T1.4 + ADR D4. Consumed by virtual module `@theo/actions`\n * (T3.1) and G4 devtools \"Actions\" tab (T5.1).\n */\nexport interface ActionManifestEntry {\n name: string\n filePath: string\n urlPath: string\n accept: 'form' | 'json'\n hasInput: boolean\n /**\n * P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n * When present, points to an isomorphic schema file at\n * `<serverDir>/actions/schemas/<basename>.ts` exporting `export const schema = z.object(...)`.\n * Virtual module emits `import {schema} from '<schemaFilePath>'` + attaches as\n * `actions.X.__zodSchema` so client-side <TheoForm> can drive zodResolver.\n * Undefined when convention not followed (graceful degrade — TheoForm still\n * works via explicit `schema={...}` prop escape hatch).\n */\n schemaFilePath?: string\n}\n\n/**\n * EC-2: structured error for scan-time defects (name collision, reserved\n * identifier, etc.). Throw at scan time to fail loud — silent shadowing is\n * a security/correctness footgun.\n */\nexport class ActionScanError extends Error {\n readonly code: 'NAME_COLLISION' | 'RESERVED_NAME'\n readonly conflictingPaths: readonly string[]\n\n constructor(\n code: 'NAME_COLLISION' | 'RESERVED_NAME',\n message: string,\n conflictingPaths: readonly string[],\n ) {\n super(message)\n // T4.1 fix: identify class by name so serverErrorToEnvelope boundary\n // translator can route via class-name lookup (without this, runtime\n // `err.name` defaults to 'Error' and the meta.name diagnostic is wrong).\n this.name = 'ActionScanError'\n this.code = code\n this.conflictingPaths = conflictingPaths\n }\n}\n\nconst ACTION_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst TEST_FILE_RE = /\\.(test|spec)\\.(ts|tsx|js|jsx)$/\nconst RESERVED_NAMES = new Set(['index', 'constructor', '__proto__', 'prototype', 'hasOwnProperty'])\n\n/**\n * Backward-compatible: original simple-shape scanner used by existing\n * consumers. Preserved verbatim; new consumers should call\n * `scanServerActionsEnriched`.\n */\nexport function scanServerActions(serverDir: string): ActionNode[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const results: ActionNode[] = []\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n let rel = relative(actionsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n results.push({\n filePath: absPath,\n actionPath: rel,\n })\n })\n return results\n}\n\n/**\n * Enriched scan: light AST detection of `accept: 'form'|'json'` + `input:`\n * presence via regex-after-comment-stripping (EC-9). Throws ActionScanError\n * on file/dir name collision (EC-2) or reserved JS identifier names.\n *\n * Output `ActionManifestEntry[]` is sorted by `name` for deterministic\n * `.theokit/actions-manifest.json` emission.\n */\nexport function scanServerActionsEnriched(serverDir: string): ActionManifestEntry[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const seenNames = new Set<string>()\n const entries: ActionManifestEntry[] = []\n\n // Collect first; collision-check after the full walk.\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n const rel = relative(actionsDir, absPath).replace(/\\\\/g, '/')\n // P#4 plugin-forms — `schemas/` subdir holds isomorphic zod schemas\n // for the shared-schema convention (T1.1). NOT executable actions; skip.\n if (rel.startsWith(`${ACTION_SCHEMAS_DIR}/`)) return\n const name = rel.slice(0, -extname(rel).length)\n\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n if (RESERVED_NAMES.has(basename)) {\n throw new ActionScanError(\n 'RESERVED_NAME',\n `Reserved JS identifier \"${basename}\" cannot be an action name (${absPath})`,\n [absPath],\n )\n }\n // File-level collision (one file appearing twice) is impossible via the\n // walker; per-export collision check happens inside the export loop below.\n const source = readFileSync(absPath, 'utf8')\n const stripped = stripComments(source)\n const accept = /\\baccept\\s*:\\s*['\"]form['\"]/.test(stripped) ? 'form' : 'json'\n const hasInput = /\\binput\\s*:\\s*z\\./.test(stripped) || /\\binput\\s*:\\s*\\w+\\(/.test(stripped)\n\n // T7.1 wire fix — extract exports so the urlPath includes the second\n // segment required by action-middleware (`/api/__actions/<file>/<export>`).\n // Each named action export becomes its own manifest entry.\n // Proxy key on the EXPORT name (so consumers write `actions.saveMemory(input)`).\n // URL keeps the runtime 2-segment shape `/api/__actions/<file>/<export>`\n // expected by action-middleware. Cross-file export collisions become a\n // scan error to surface the ambiguity early.\n const exportNames = extractActionExportNames(stripped)\n // P#4 plugin-forms shared-schema convention: check for\n // `<actionsDir>/schemas/<basename>.ts` (or .tsx/.js/.jsx).\n // Skip when actions live in subdirs (`schemas/` is flat by convention).\n const schemaFilePath = name.includes('/') ? undefined : detectSchemaFile(actionsDir, basename)\n for (const exportName of exportNames) {\n const proxyKey = exportName === 'default' ? name : exportName\n if (seenNames.has(proxyKey)) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Duplicate action proxy key \"${proxyKey}\" (two files export the same name)`,\n [absPath],\n )\n }\n seenNames.add(proxyKey)\n const entry: ActionManifestEntry = {\n name: proxyKey,\n filePath: absPath,\n urlPath: `/api/__actions/${name}/${exportName}`,\n accept,\n hasInput,\n }\n if (schemaFilePath !== undefined) {\n entry.schemaFilePath = schemaFilePath\n }\n entries.push(entry)\n }\n })\n\n // EC-2: detect file vs dir collisions (e.g., foo.ts AND foo/bar.ts).\n // After walk completes, any name that has children prefixed `name/` triggers collision.\n for (const entry of entries) {\n const childPrefix = `${entry.name}/`\n const conflictingChild = entries.find((other) => other.name.startsWith(childPrefix))\n if (conflictingChild) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Action \"${entry.name}\" conflicts with directory of same name containing \"${conflictingChild.name}\"`,\n [entry.filePath, conflictingChild.filePath],\n )\n }\n }\n\n entries.sort((a, b) => {\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n return 0\n })\n return entries\n}\n\n/**\n * Strip JavaScript line + block comments from source. Simple state machine\n * (does not parse strings — false positives if a comment marker appears\n * inside a string literal, but that's an acceptable trade-off for v1 vs\n * full AST parse).\n */\n/**\n * Extract action export names via regex over comment-stripped source.\n * Matches `export const <name> = defineAction(...)`, `export default\n * defineAction(...)`, and `export function <name>(...)` forms. Returns\n * `['default']` when no named action exports are found (best-effort fallback\n * for default-export shapes the regex misses).\n */\nfunction extractActionExportNames(stripped: string): string[] {\n const names = new Set<string>()\n const namedRe = /\\bexport\\s+(?:const|let|var|function\\*?)\\s+([a-zA-Z_$][\\w$]*)\\s*[=(]/g\n let m: RegExpExecArray | null\n while ((m = namedRe.exec(stripped)) !== null) {\n const name = m[1]\n if (typeof name === 'string' && name.length > 0) names.add(name)\n }\n if (/\\bexport\\s+default\\s+defineAction\\b/.test(stripped)) {\n names.add('default')\n }\n if (names.size === 0) names.add('default')\n return [...names]\n}\n\n/**\n * P#4 plugin-forms shared-schema convention helper (per plan p4-plugin-forms v1.1 T1.1).\n * Returns the resolved path to `<actionsDir>/schemas/<basename>.<ext>` if it exists.\n * Tries `.ts`, `.tsx`, `.js`, `.jsx` in that order. Returns undefined when no match.\n */\nfunction detectSchemaFile(actionsDir: string, basename: string): string | undefined {\n const schemasDir = join(actionsDir, ACTION_SCHEMAS_DIR)\n if (!existsSync(schemasDir)) return undefined\n for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {\n const candidate = join(schemasDir, `${basename}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction stripComments(source: string): string {\n let out = ''\n let i = 0\n while (i < source.length) {\n const ch = source[i]\n const next = source[i + 1]\n if (ch === '/' && next === '/') {\n // Line comment: skip until newline\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (ch === '/' && next === '*') {\n // Block comment: skip until */\n i += 2\n while (i < source.length - 1 && !(source[i] === '*' && source[i + 1] === '/')) i++\n i += 2\n continue\n }\n out += ch\n i++\n }\n return out\n}\n","export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n /**\n * The subset of `methods` whose policy is the literal `'public'` (ADR 0001).\n *\n * `undefined` means \"not detected\" — a manifest built before the field existed — and is NOT the\n * same as `[]`, which means \"detected, and nothing is public\". `public-exposure-gate.ts` depends\n * on that distinction: reading absence as safety is the failure it exists to prevent.\n */\n publicMethods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport type { HttpMethod } from '../../core/contracts/http-methods.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { detectRoutePolicyKinds, type RoutePolicyKind } from './detect-route-policy.js'\nimport {\n MissingRoutePolicyError,\n RedundantApiSegmentError,\n RouterConventionError,\n} from './errors.js'\nimport { createFileStampCache } from './file-stamp-cache.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\n/**\n * `routes/api/…` doubles the prefix `fileToRoutePath` adds unconditionally.\n *\n * Checked on the FIRST segment only. A file merely named `api-keys.ts` or `apiary.ts` is the\n * author's to choose and resolves fine; what doubles is the DIRECTORY, because it prefixes every\n * file beneath it — and carries the same redundant segment into the generated typed client.\n */\nfunction assertNoRedundantApiSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const [first, ...rest] = rel.split('/')\n if (first !== 'api' || rest.length === 0) return\n\n const withoutExt = rel.slice(0, -extname(rel).length)\n throw new RedundantApiSegmentError({\n file: filePath,\n doubledRoutePath: `/api/${withoutExt}`,\n doubledClientChain: `client.${withoutExt.split('/').join('.')}`,\n suggestion: `routes/${rest.join('/')}`,\n })\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\n/**\n * ADR 0001, Decision point 5 — a route declares its policy explicitly, `public`\n * included, and absence stops meaning open.\n *\n * The refusal lives here, next to the dotted-basename refusal, because this is\n * the one place every entry point passes through: `theo build`, `theo start`,\n * `theo dev`, `theo routes` and each deployment adapter all reach routes by\n * calling `scanServerRoutes`. A gate wired into the build command instead would\n * have to be remembered by six adapters that generate their own entry file, and\n * a gate you can forget to call is a gate that reports on the routes somebody\n * remembered.\n *\n * The blast radius stops at the file system. A `RouteConfig` built in memory and\n * handed to `executeWebRequest` or `callProcedure` never passed a scanner, so it\n * never reaches this function — the runtime still treats an undeclared policy as\n * \"not declared\", exactly as `evaluateRoutePolicy` documents. Absence is refused\n * where an application DECLARES its routes, not where a caller passes one.\n */\nfunction assertEveryMethodDeclaresPolicy(\n filePath: string,\n routePath: string,\n { methods, declaredPolicy }: RouteSourceFacts,\n): void {\n if (methods.length === 0) return\n const missing = methods.filter((method) => !declaredPolicy.has(method))\n if (missing.length === 0) return\n throw new MissingRoutePolicyError({ file: filePath, routePath, methods: missing })\n}\n\n/** What the two AST passes over a route file answer, together. */\ninterface RouteSourceFacts {\n methods: HttpMethod[]\n /** The subset of `methods` that declared a `policy`. Empty when there are no methods. */\n declaredPolicy: Set<HttpMethod>\n /** What each declaring method declared. Keys are exactly `declaredPolicy`. */\n policyKinds: Map<HttpMethod, RoutePolicyKind>\n}\n\n/**\n * Per-file facts, recomputed only when the file changes (usetheokit/theokit#417).\n *\n * `theokit dev` calls `scanServerRoutes` on EVERY request, and this function parsed each route\n * file with the TypeScript AST TWICE per call — once for the exported methods, once for the policy\n * gate, sharing the source string but not the parse. `agent-scan.ts` had solved exactly this for\n * agent files and wrote down why; routes, which an application has far more of, had neither the\n * cache nor the reasoning.\n *\n * The refusal is deliberately NOT cached — only the facts are. A scan that found a missing policy\n * once must go on refusing on every later scan, so `assertEveryMethodDeclaresPolicy` re-derives the\n * set difference (cheap, no parse) from the cached facts each time.\n */\nconst routeFactsCache = createFileStampCache<RouteSourceFacts>()\n\n/** Test seam — the module-level cache would otherwise outlive a fixture directory. */\nexport function _resetRouteScanCacheForTests(): void {\n routeFactsCache.clear()\n}\n\nfunction routeSourceFacts(absPath: string): RouteSourceFacts {\n return routeFactsCache.get(absPath, () => {\n const source = readFileSync(absPath, 'utf-8')\n const methods = detectExportedHttpMethods(absPath, source)\n // Kept lazy exactly as the early return in the assert made it: a file exporting no HTTP method\n // has nothing for the policy gate to check, and parsing it a second time to learn that was the\n // cost this cache exists to remove.\n const policyKinds =\n methods.length === 0\n ? new Map<HttpMethod, RoutePolicyKind>()\n : detectRoutePolicyKinds(absPath, source)\n // One parse, one source of truth: the gate's set is a projection of the map, so the build gate\n // (\"did it declare?\") and the exposure gate (\"what did it declare?\") cannot drift apart.\n return { methods, declaredPolicy: new Set(policyKinds.keys()), policyKinds }\n })\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoRedundantApiSegment(absPath, routesDir)\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const facts = routeSourceFacts(absPath)\n const { methods } = facts\n assertEveryMethodDeclaresPolicy(absPath, routePath, facts)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n // Always set, even when empty — `public-exposure-gate.ts` reads the FIELD's presence as\n // \"this was measured\" and its absence as \"this predates the check\". A scanned route was\n // measured by definition, so it never reports the second.\n publicMethods: [...facts.policyKinds]\n .filter(([, kind]) => kind === 'public')\n .map(([method]) => method),\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n results.sort((a, b) => compareRouteSpecificity(a.routePath, b.routePath))\n\n return results\n}\n\n/** How constrained one path segment is. Lower is more specific. */\nconst SEGMENT_STATIC = 0\nconst SEGMENT_DYNAMIC = 1\nconst SEGMENT_CATCH_ALL = 2\n\nfunction segmentSpecificity(segment: string): number {\n if (segment.startsWith(':...')) return SEGMENT_CATCH_ALL\n if (segment.startsWith(':')) return SEGMENT_DYNAMIC\n return SEGMENT_STATIC\n}\n\n/**\n * Order two route paths by specificity, most specific first.\n *\n * `matchRoute` returns on the first pattern that matches, so this order IS the\n * precedence contract — not a presentation detail of the manifest.\n *\n * Segments are compared position by position, and the first position where they\n * disagree decides: a literal beats a parameter, and a parameter beats a\n * catch-all. That is the rule the URL itself expresses, and it is the rule a\n * whole-path comparison cannot express, because a whole-path comparison reads\n * characters across segment boundaries. Comparing `/api/:resource/settings`\n * with `/api/users/:id` that way put the generic route first — `:` precedes `u`\n * in every collation — so `/api/users/settings` reached the generic handler and\n * an authorization check placed on the specific route was bypassed\n * (usetheokit/theokit#348).\n *\n * Only the segments the two paths have in common are ranked. Past that point\n * one path is a strict prefix of the other, and two such routes cannot match\n * the same URL — so there is nothing to decide, only a stable order to pick.\n *\n * The final fallback compares by code unit rather than with `localeCompare`.\n * Collation is locale-dependent, so the same route table would order\n * differently under a different `LANG` — the cross-machine divergence\n * usetheokit/theokit#346 removed from the sibling scanner. The tiebreak only\n * has to be total and stable; it does not have to be alphabetical for a human.\n */\nexport function compareRouteSpecificity(a: string, b: string): number {\n const aSegments = a.split('/')\n const bSegments = b.split('/')\n const shared = Math.min(aSegments.length, bSegments.length)\n\n for (let i = 0; i < shared; i++) {\n const difference = segmentSpecificity(aSegments[i]) - segmentSpecificity(bSegments[i])\n if (difference !== 0) return difference\n }\n\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * A CJS require keeps the package on its native path, and it is taken LAZILY — see `ts_()` below\n * for what module-scope cost on Cloudflare Workers. The type-only namespace import gives us the AST\n * helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\nimport { compareByCodeUnit } from '../_internal/compare-by-code-unit.js'\n\n/**\n * The compiler, loaded on first use and never at import time.\n *\n * This was module scope, and a Cloudflare deploy found what that costs: `theokit/server/scan` is\n * imported by the generated worker, Cloudflare EXECUTES the module during validation, and there\n * `import.meta.url` is undefined — so `createRequire` threw before the worker answered a request\n * (B-263, error 10021). `wrangler deploy --dry-run` returns exit 0 on the same bundle: it bundles\n * and does not execute.\n *\n * The second reason holds regardless of Workers: this is a BUILD-TIME AST scanner, and nothing that\n * merely imports it should pay for the TypeScript compiler.\n *\n * Cached, so scanning many files still loads it once.\n */\nlet compiler: typeof TS | undefined\n\nfunction ts_(): typeof TS {\n compiler ??= createRequire(import.meta.url)('typescript') as typeof TS\n return compiler\n}\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts_().SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\n// An AST visitor over the export forms (const / function / named export) is inherently branchy;\n// the complexity is in the grammar it walks, not in tangled logic. Splitting it would scatter one\n// cohesive dispatch across helpers for no readability gain.\n// eslint-disable-next-line complexity -- AST visitor, see above\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts_().isVariableStatement(stmt) && hasExportModifier(ts_().getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts_().isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts_().isFunctionDeclaration(stmt) || ts_().isClassDeclaration(stmt)) &&\n hasExportModifier(ts_().getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (\n ts_().isExportDeclaration(stmt) &&\n stmt.exportClause &&\n ts_().isNamedExports(stmt.exportClause)\n ) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n // `filePath` is a route module the framework itself discovered by globbing the project — never\n // user input at runtime. Reading it by a computed path is the whole job of a source scanner.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- framework-controlled path\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts_().createSourceFile(\n filePath,\n src,\n ts_().ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts_().ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n // HTTP method names are ASCII, so no collation disagrees about them today.\n // Ordered by code unit anyway, so that \"build output orders by code unit\"\n // stays a rule a reader can check by grep rather than a rule with exceptions\n // each needing its own argument (usetheokit/theokit#351).\n return [...found].sort(compareByCodeUnit)\n}\n","/**\n * Detect which HTTP-method exports of a route file declare an access policy.\n *\n * The companion of `detect-http-methods.ts`, and deliberately the same shape:\n * the TypeScript AST rather than a regex, because `// policy: 'public'` in a\n * comment and `` `policy` `` in a doc string are both things a route file\n * legitimately contains, and neither declares anything.\n *\n * ADR 0001 decides that a route says who may call it, `'public'` included, and\n * that absence stops meaning open. This is the half of that decision the\n * scanner can answer: which exports said something. `scanServerRoutes` turns\n * the silence into a build error.\n *\n * ## What counts as a declaration\n *\n * Two forms, because the framework ships two ways to write a route:\n *\n * ```ts\n * export const GET = route().policy('public').handler(fn).build() // builder\n * export const GET = defineRoute({ policy: fn, handler: fn }) // object\n * ```\n *\n * Only the config position is inspected — the top level of an object literal\n * passed to a call, and `.policy(...)` links of a call chain. The handler's\n * BODY is never walked, so a handler that happens to return `{ policy: ... }`\n * does not satisfy the gate. Reading deeper would trade a false build error for\n * a false pass, and a false pass here is a route nobody protected that the gate\n * reported as protected.\n *\n * ## What it deliberately cannot see\n *\n * `export { GET } from './shared'` re-exports across a module boundary, and\n * `export const GET = makeAdminRoute(fn)` hides the config inside a helper.\n * Both come back \"not declared\", which fails the build. That direction is the\n * chosen one: the cost is an explicit declaration on a route that already had\n * one somewhere else, and the alternative cost is a route reported as declared\n * because the scanner guessed.\n */\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\n/**\n * The compiler, loaded on first use and never at import time.\n *\n * This was module scope, and a Cloudflare deploy found what that costs: `theokit/server/scan` is\n * imported by the generated worker, Cloudflare EXECUTES the module during validation, and there\n * `import.meta.url` is undefined — so `createRequire` threw before the worker answered a request\n * (B-263, error 10021). `wrangler deploy --dry-run` returns exit 0 on the same bundle: it bundles\n * and does not execute.\n *\n * The second reason holds regardless of Workers: this is a BUILD-TIME AST scanner, and nothing that\n * merely imports it should pay for the TypeScript compiler.\n *\n * Cached, so scanning many files still loads it once.\n */\nlet compiler: typeof TS | undefined\n\nfunction ts_(): typeof TS {\n compiler ??= createRequire(import.meta.url)('typescript') as typeof TS\n return compiler\n}\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nconst POLICY_KEY = 'policy'\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts_().SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\n/** Strip the wrappers that carry no runtime meaning (`(x)`, `x as T`, `x satisfies T`, `x!`). */\nfunction unwrap(expression: TS.Expression): TS.Expression {\n let current = expression\n for (;;) {\n if (\n ts_().isParenthesizedExpression(current) ||\n ts_().isAsExpression(current) ||\n ts_().isSatisfiesExpression(current) ||\n ts_().isNonNullExpression(current)\n ) {\n current = current.expression\n continue\n }\n return current\n }\n}\n\nfunction propertyNameIsPolicy(name: TS.PropertyName | undefined): boolean {\n if (name === undefined) return false\n if (ts_().isIdentifier(name)) return name.text === POLICY_KEY\n if (ts_().isStringLiteral(name)) return name.text === POLICY_KEY\n return false\n}\n\n/**\n * What a route said about who may call it.\n *\n * `'public'` is the string literal ADR 0001 gives a meaning to; everything else is a function this\n * pass cannot evaluate, and is reported `'guarded'`. That asymmetry is deliberate — see\n * `policyKindOfArgument`.\n */\nexport type RoutePolicyKind = 'public' | 'guarded'\n\n/**\n * The kind an argument in the `policy` position declares.\n *\n * Only the bare literal `'public'` is read as open. An identifier, a property access, a call, a\n * template — anything this pass cannot evaluate — comes back `'guarded'`.\n *\n * The two mistakes are not symmetric. Labelling an open route `'guarded'` costs a gate that fails\n * to fire on a route somebody left open on purpose. Labelling a guarded route `'public'` would put\n * a protected route on a list of exposures and, worse, train a reader to disbelieve the list. Only\n * the literal is legible, so only the literal is believed.\n */\nfunction policyKindOfArgument(expression: TS.Expression): RoutePolicyKind {\n const arg = unwrap(expression)\n return ts_().isStringLiteral(arg) && arg.text === 'public' ? 'public' : 'guarded'\n}\n\n/** A `policy` key at the TOP level of a config object literal. Never deeper. */\nfunction objectPolicyKind(literal: TS.ObjectLiteralExpression): RoutePolicyKind | undefined {\n for (const property of literal.properties) {\n if (ts_().isSpreadAssignment(property)) continue\n if (!propertyNameIsPolicy(property.name)) continue\n // `{ policy: x }` carries an initializer; `{ policy }` shorthand does not, and a shorthand\n // reference is exactly the unreadable case that takes the safe label.\n return ts_().isPropertyAssignment(property)\n ? policyKindOfArgument(property.initializer)\n : 'guarded'\n }\n return undefined\n}\n\n/**\n * Does this initializer declare a policy?\n *\n * Walks the call chain leftwards (`.build()` -> `.handler()` -> `.policy()`) and\n * checks the arguments each call receives, which is where the object form puts\n * its config. Nothing else is visited.\n */\nfunction policyKind(expression: TS.Expression): RoutePolicyKind | undefined {\n const expr = unwrap(expression)\n\n if (ts_().isObjectLiteralExpression(expr)) return objectPolicyKind(expr)\n\n if (!ts_().isCallExpression(expr)) return undefined\n\n const callee = unwrap(expr.expression)\n\n if (ts_().isPropertyAccessExpression(callee) && callee.name.text === POLICY_KEY) {\n // `.policy()` with no argument declares the key and says nothing readable.\n return expr.arguments.length === 0 ? 'guarded' : policyKindOfArgument(expr.arguments[0])\n }\n\n for (const argument of expr.arguments) {\n const arg = unwrap(argument)\n if (!ts_().isObjectLiteralExpression(arg)) continue\n const kind = objectPolicyKind(arg)\n if (kind !== undefined) return kind\n }\n\n // Keep walking the chain: `route().policy(p).handler(h).build()` reaches\n // `.policy` only by stepping left through `.build` and `.handler`.\n if (ts_().isPropertyAccessExpression(callee)) return policyKind(callee.expression)\n\n return undefined\n}\n\n/** Every top-level `const x = <expr>` in the file, so `export { x as GET }` can be resolved. */\nfunction collectLocalInitializers(sourceFile: TS.SourceFile): Map<string, TS.Expression> {\n const locals = new Map<string, TS.Expression>()\n for (const stmt of sourceFile.statements) {\n if (!ts_().isVariableStatement(stmt)) continue\n for (const decl of stmt.declarationList.declarations) {\n if (ts_().isIdentifier(decl.name) && decl.initializer !== undefined) {\n locals.set(decl.name.text, decl.initializer)\n }\n }\n }\n return locals\n}\n\n/** `export const GET = ...` */\nfunction collectFromVariableStatement(\n stmt: TS.VariableStatement,\n declared: Map<HttpMethod, RoutePolicyKind>,\n): void {\n if (!hasExportModifier(ts_().getModifiers(stmt))) return\n for (const decl of stmt.declarationList.declarations) {\n if (!ts_().isIdentifier(decl.name) || !HTTP_METHOD_NAMES.has(decl.name.text)) continue\n if (decl.initializer === undefined) continue\n const kind = policyKind(decl.initializer)\n if (kind !== undefined) declared.set(decl.name.text as HttpMethod, kind)\n }\n}\n\n/**\n * `export { GET }` / `export { handler as GET }` — resolvable only when the\n * declaration is in this file. A re-export with a module specifier is not.\n */\nfunction collectFromExportDeclaration(\n stmt: TS.ExportDeclaration,\n locals: Map<string, TS.Expression>,\n declared: Map<HttpMethod, RoutePolicyKind>,\n): void {\n if (stmt.moduleSpecifier !== undefined) return\n if (!stmt.exportClause || !ts_().isNamedExports(stmt.exportClause)) return\n for (const spec of stmt.exportClause.elements) {\n if (!HTTP_METHOD_NAMES.has(spec.name.text)) continue\n const local = locals.get((spec.propertyName ?? spec.name).text)\n if (local === undefined) continue\n const kind = policyKind(local)\n if (kind !== undefined) declared.set(spec.name.text as HttpMethod, kind)\n }\n}\n\nfunction collectFromStatement(\n stmt: TS.Statement,\n locals: Map<string, TS.Expression>,\n declared: Map<HttpMethod, RoutePolicyKind>,\n): void {\n if (ts_().isVariableStatement(stmt)) {\n collectFromVariableStatement(stmt, declared)\n return\n }\n if (ts_().isExportDeclaration(stmt)) {\n collectFromExportDeclaration(stmt, locals, declared)\n }\n // `export function GET() {}` and `export class GET {}` reach neither branch,\n // on purpose: a bare function has no config object to carry a policy.\n}\n\n/**\n * What each HTTP-method export of this file declared — `'public'` or `'guarded'`.\n *\n * The KEYS answer the question ADR 0001 asks at build time: which methods declared a policy at all.\n * The VALUES answer the half it left open until now: whether the declaration protects anything.\n * One map, so the build gate and the exposure gate cannot disagree about which methods declared.\n *\n * `content` is passed in rather than read here so the scanner reads each route file once and hands\n * the same source to both detectors.\n */\nexport function detectRoutePolicyKinds(\n filePath: string,\n content: string,\n): Map<HttpMethod, RoutePolicyKind> {\n const sourceFile = ts_().createSourceFile(\n filePath,\n content,\n ts_().ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts_().ScriptKind.TS,\n )\n const locals = collectLocalInitializers(sourceFile)\n const declared = new Map<HttpMethod, RoutePolicyKind>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, locals, declared)\n }\n return declared\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["basename","existsSync","readFileSync","statSync","extname","join","relative","readFileSync","readFileSync","createRequire","compiler","ts_","createRequire","HTTP_METHOD_NAMES","hasExportModifier","collectFromStatement","relative","extname","readFileSync","relative","extname","join","existsSync","statSync","existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/adapters/types.ts
5
+ var RENDERS_DOCUMENT = /* @__PURE__ */ new Set([
6
+ "cloudflare",
7
+ "vercel",
8
+ "node",
9
+ "static",
10
+ "bun"
11
+ ]);
12
+ function targetRendersDocument(target) {
13
+ return RENDERS_DOCUMENT.has(target);
14
+ }
15
+ var VALID_TARGETS = [
16
+ "node",
17
+ "vercel",
18
+ "cloudflare",
19
+ "static",
20
+ "bun",
21
+ "deno-deploy",
22
+ "netlify",
23
+ "aws-lambda",
24
+ "theo-cloud"
25
+ ];
26
+
27
+ export {
28
+ targetRendersDocument,
29
+ VALID_TARGETS
30
+ };
31
+ //# sourceMappingURL=chunk-4X43VTST.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/types.ts"],"sourcesContent":["import type { Plugin } from 'vite'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\n\nimport type { DeployedAgent } from './deployed-agents.js'\nimport type { DeployedCorsOptions } from './deployed-cors.js'\nimport type { DeployedCsrfOptions } from './deployed-csrf.js'\nimport type { DeployedRateLimitOptions } from './deployed-rate-limit.js'\nimport type {\n DeployedAgentsDirOptions,\n DeployedRuntimeConfigOptions,\n DeployedServerDirOptions,\n} from './deployed-runtime-config.js'\n\n/**\n * Everything a filesystem deploy entry accepts.\n *\n * `vercel`, `aws-lambda` and `deno-deploy` declared this same six-member intersection, character\n * for character. Extracted after SonarCloud's duplication gate named it among the repeated blocks\n * on a pull request — the gate measuring something real, because a member added to the set had to\n * be added in three places or the targets would silently disagree about what they accept.\n *\n * `netlify` is deliberately NOT a user: it resolves `serverDir` as a literal `resolve(cwd, 'server')`\n * rather than from configuration, so it carries no `DeployedServerDirOptions`. Widening this type to\n * cover it would let netlify accept a `serverDir` it then ignores, which is the accepted-and-ignored\n * failure this framework refuses elsewhere. That netlify cannot take one is a separate gap, not a\n * reason to lie about the type.\n */\nexport type DeployedEntryOptions = {\n securityHeaders?: SecurityHeadersConfig\n} & DeployedAgentsDirOptions &\n DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedServerDirOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions\n\n/**\n * Build context injected by the CLI into adapter.build.\n *\n * - `makeVitePlugins` — optional factory provided by CLI for adapters\n * that drive `viteBuild()` directly (currently: `nodeAdapter`).\n * When omitted, adapters that need Vite must fail with an actionable\n * error. This inverts the previous direct edge `adapters → vite-plugin`\n * per ADR-0001 v3 (T1.1 of the architecture-cleanup plan).\n *\n * The factory MAY return a Promise — `theokit build` uses\n * `theoPluginAsync` to wire the full Plugin[] chain (actions virtual\n * module + typed client + services + @theokit/ui auto-chain) so adapters\n * MUST `await` the result. See `cli/commands/build.ts` for the canonical\n * invocation.\n */\n/** One server route, flattened to what an adapter can bake into an emitted entry (#369). */\nexport interface AdapterRoute {\n /** Path relative to the project root — both the import specifier and the executor's lookup key. */\n filePath: string\n routePath: string\n methods: readonly string[]\n}\n\nexport interface AdapterBuildContext {\n /**\n * `ssrStreaming` is here because the flag decides WHICH server entry the plugin emits, and\n * without a field for it no caller could pass one. `vite-plugin/index.ts` reads\n * `options.ssrStreaming === true`, so an absent field evaluated `undefined === true` forever:\n * a project declaring `ssrStreaming: true` got the buffering entry, its server found no\n * streaming renderer to call, and every response was the fully buffered document. Measured\n * 2026-09-24 on a released build — TTFB tracked the slowest Suspense boundary (60ms delay ->\n * 73ms, 800ms -> 815ms) with a single socket arrival. Same shape as `#95`, which added the\n * three directories to this object for the same reason.\n */\n makeVitePlugins?: (opts: {\n root: string\n ssr?: boolean\n ssrStreaming?: boolean\n }) => Plugin[] | Promise<Plugin[]>\n /**\n * Scan the project's routes, INJECTED for the same reason `makeVitePlugins` is (#369).\n *\n * A Worker has no filesystem, so the Cloudflare entry has to bake its routes at build time\n * instead of scanning for them at request time. Importing the scanner here would put an\n * `adapters → server` edge in the graph — the layering inversion ADR-0001 v3 removed for\n * `vite-plugin`, and the one `adapters-may-only-depend-on-core-router-services` refuses. The CLI\n * already imports both sides, so it composes this and passes it in.\n */\n scanRoutes?: (serverDir: string) => {\n routes: AdapterRoute[]\n wsRoutes: string[]\n /**\n * The app's agents (#367). A different scan from the routes, served by a different function —\n * which is precisely why no adapter had ever heard of them and every deployed\n * `/api/agents/<name>` was a 404.\n */\n agents?: DeployedAgent[]\n /**\n * B-185 — the app's `server/context.ts`, project-relative, or `undefined` when it has none.\n *\n * A Worker has no filesystem on which to find it at request time, so a target that bakes its\n * agents bakes this too (ADR 0014) and hands the factory to the subject resolver. Decided HERE,\n * beside the agents, because this provider is the only place that holds `serverDir` — the same\n * inversion argument the `agents` field above records for #367.\n */\n contextModule?: string\n }\n}\n\n/**\n * A configuration key that a request handler applies at runtime. Build-time\n * keys are deliberately absent: a runtime cannot silently drop them.\n */\nexport type ConfigConcern =\n | 'rateLimit'\n | 'cors'\n | 'csrf'\n | 'disallowed'\n | 'serialization'\n | 'plugins'\n | 'securityHeaders'\n\nexport interface DeployAdapter {\n name: string\n /**\n * #382 — does the handler this adapter emits hand its runtime a response\n * whose body is still being written?\n *\n * The claim is deliberately narrow, and it is the only part we can verify\n * without a real deployment: `true` means the emitted contract carries a\n * live body (a `Response` over a `ReadableStream`, or a chunk-by-chunk drain\n * into the runtime's own writable) rather than a fully materialized string\n * or buffer. It does NOT claim the platform was observed flushing early —\n * that needs a deploy, and none of these targets has one in CI.\n *\n * Omitted means no, on purpose: a new adapter should have to state that it\n * streams, because the failure mode of the opposite default is a target\n * silently listed for something nobody exercised.\n *\n * `aws-lambda` is the one target that answers no by construction — its v2\n * result object carries `body` as a string, so the response cannot exist\n * before the run ends. Making it stream means `awslambda.streamifyResponse`\n * plus a Function URL in `RESPONSE_STREAM` invoke mode, which this adapter\n * does not emit and which would break every API Gateway deployment of it.\n */\n streamsResponses?: boolean\n /**\n * Which configuration keys the handler this adapter emits actually applies.\n *\n * Same contract as `streamsResponses`, for the same reason: omitted means\n * **none**, on purpose. A new adapter should have to state what it honours,\n * because the failure mode of the opposite default is a target that parses a\n * rate limit, validates it, and refuses nothing — with no line anywhere\n * saying so.\n *\n * `'runtime-not-emitted-here'` is a third answer, not a synonym for none: it\n * belongs to an adapter that emits no request handler at all, so this build\n * genuinely cannot say what the runtime applies. Reporting such a target as\n * dropping configuration would be asserting rather than measuring.\n *\n * The declaration is a claim, and nothing here can verify it — a wrong claim\n * reads exactly like a right one. What it buys is that dropping a concern\n * becomes a visible edit instead of an omission.\n */\n /**\n * #367 — can the handler this adapter emits serve an AGENT?\n *\n * Same contract as `streamsResponses` and `appliesConfig`, for the same reason: omitted means\n * **no**, on purpose. The gap this answers went unnoticed because nothing in this layer had ever\n * heard of agents — `grep -rc \"agent\"` over the 14 adapter files returned nothing — so an agent\n * was served by no pipeline at all outside a machine running `theokit start`, in a framework\n * whose stated reason to exist is that the agent ships through the same pipeline as the page.\n *\n * `true` claims the emitted entry routes `/api/agents/<name>` to `mountAgent`. It is a claim\n * nothing here can verify, exactly like the other two; what it buys is that a target which drops\n * agents has to say so rather than be silently assumed to serve them.\n */\n servesAgents?: boolean\n appliesConfig?: readonly ConfigConcern[] | 'runtime-not-emitted-here'\n\n /**\n * B-257 — can the emitted entry ENFORCE a declared rate limit, and under what condition?\n *\n * Separate from `appliesConfig` because the two answer different questions and one list could not\n * carry both. `findUnappliedConfig` reads the first to WARN; `assertRateLimitEnforceable` reads\n * this one to REFUSE. Welded, adding `'rateLimit'` to stop a warning also satisfied\n * `applied.includes('rateLimit')` and returned before the throw — switching the refusal off on a\n * runtime where the limit still cannot hold. `config-support.ts:213-215` wrote that refusal\n * because \"`rateLimit` is the one whose absence looks exactly like success\".\n *\n * `'always'` a long-lived process; the in-process counter survives between requests.\n * `'never'` this adapter emits no request handler that could enforce anything. A\n * TRUE claim, unlike the abstain below: nothing answers for a static\n * export at runtime, so there is no other runtime to defer to.\n * `'with-a-store'` a per-invocation or per-isolate runtime. It enforces only when the config\n * names a durable store, because an in-process counter there forgets, and a\n * limit that forgets is a limit that does not limit.\n * `'not-ours-to-judge'` this adapter emits no request handler OF ITS OWN, and another runtime\n * answers for it. An ABSTAIN: `config-support.ts:105-108` calls that \"a\n * different fact from saying no\", and refusing there would assert something\n * unmeasured about somebody else's runtime.\n *\n * **Omitted REFUSES**, and it does not abstain. Abstaining is a claim about another runtime, and a\n * claim is made rather than inferred from silence. The first cut defaulted to the abstain and\n * inverted the rule it replaced: `appliesConfig ?? []` refused an adapter that declared nothing,\n * while `?? 'not-ours-to-judge'` let it through. Measured on `static.ts`, which declares neither\n * field and is registered — it went from refusing a declared limit to proceeding with a warning.\n * This member is on the EXPORTED interface, so every third-party adapter inherited that inversion.\n */\n enforcesRateLimit?: 'always' | 'never' | 'with-a-store' | 'not-ours-to-judge'\n\n /**\n * Whether the deployed entry can read the project's SOURCE TREE while serving a request.\n *\n * The question behind four separate repairs, asked too late every time:\n *\n * cloudflare #369 / #367 routes and agents baked\n * vercel B-319 routes baked\n * netlify B-338 routes and agents baked\n * aws-lambda B-344 routes and agents baked\n *\n * `false` means the entry MUST bake its routes and agents at build time. A runtime\n * `scanServerRoutes` is a `readdirSync`, and where the artifact was uploaded alone there is nothing\n * to read: measured on netlify and aws-lambda, every `/api/*` answered its own 404. Where a tree IS\n * carried the route files are TypeScript, which a plain Node runtime cannot compile — the\n * `SyntaxError: Unexpected token ')'` netlify answered with before B-338.\n *\n * `true` means the project directory is present at run time, so the scan is legitimate and cheaper\n * than baking. `bun` and `deno-deploy` are the two, and `deno-deploy` can additionally compile the\n * TypeScript it finds.\n *\n * **Omitted is neither answer**, and `a-deployed-entry-is-self-contained-where-it-must-be.test.ts`\n * refuses it. This follows `enforcesRateLimit`'s own lesson one field up: its first cut defaulted to\n * the permissive value and inverted the rule it replaced. A default of `true` would excuse a scan on\n * a platform that cannot serve it; a default of `false` would demand baking from an adapter that\n * has no reason to. Neither silence is safe, so silence is refused.\n */\n readsSourceAtRunTime?: boolean\n\n /**\n * Who makes the entry's bare specifiers resolvable.\n *\n * The entry imports `theokit/server/scan` and its siblings by bare specifier, and whether that\n * breaks depends entirely on who resolves it afterwards — the table `docs/adr/0020` opens with:\n *\n * `'this-adapter'` nobody else does, so the adapter bundles the entry itself through\n * `bundleDeployedFunction`. Omitting the call ships source, and the\n * platform answers\n * `ERR_MODULE_NOT_FOUND: Cannot find package 'theokit'` — measured on\n * vercel (B-316), netlify (B-339) and aws-lambda (B-342).\n * `'the-platform'` the platform's own bundler resolves at deploy time. `cloudflare`\n * only: wrangler runs esbuild, validated live on 2026-09-26. Bundling\n * twice would be work with no observable effect.\n * `'the-project-at-run-time'` the project's `node_modules` is present when the entry runs, so\n * nothing has to be inlined. `bun` and `deno-deploy`.\n *\n * **Omitted is refused**, for the reason given on the field above: an entry whose specifiers nobody\n * resolves does not fail late, it fails at import, and a silence that reads as one of the three\n * answers would let that ship.\n */\n specifiersResolvedBy?: 'this-adapter' | 'the-platform' | 'the-project-at-run-time'\n /**\n * Why THIS target cannot stream, in the words its own maintainer would use.\n *\n * The refusal is computed centrally so it can run before anything is destroyed (B-336), and a central\n * check cannot know that Lambda's v2 result object carries the body as a string or that\n * `awslambda.streamifyResponse` is what would be needed. Declared here for the same reason\n * `streamsResponses` is: the adapter is the only thing that knows.\n *\n * Read only when `streamsResponses` is not `true`. Absent is fine — the generic refusal still names\n * the target, the config key and the alternatives.\n */\n streamingUnsupportedDetail?: string\n build(config: TheoConfig, cwd: string, ctx?: AdapterBuildContext): Promise<void>\n}\n\nexport type BuildTarget =\n | 'node'\n | 'vercel'\n | 'cloudflare'\n | 'static'\n | 'bun'\n | 'deno-deploy'\n | 'netlify'\n | 'aws-lambda'\n | 'theo-cloud'\n\n/**\n * Whether a target's emitted entry renders the HTML document on the server.\n *\n * The build printed `(SSR)` from `config.ssr` alone, so a target that delegates the document to a\n * static host was announced as server-rendering. Measured 2026-09-28 on a real project built with\n * `--target bun` and driven under Bun 1.3.14: the build said `✓ Build complete → bun (SSR)` and `GET /`\n * answered 531 bytes whose body was an empty `<div id=\"root\">`.\n *\n * This answers CAN the target render, not WILL it on this build — the config flag that turns rendering\n * on differs per target (`ssrStreaming` for Cloudflare, `ssr` for the rest), and each adapter already\n * knows its own. A caller wanting the second question asks both.\n *\n * Kept beside `VALID_TARGETS` rather than on `DeployAdapter`, because the CLI prints the note before\n * and after the adapter runs and never holds the emitted text. `check` in the test suite compares this\n * table against what each adapter's source actually reaches, in both directions, so it cannot drift\n * into describing a fleet that moved.\n */\nconst RENDERS_DOCUMENT: ReadonlySet<BuildTarget> = new Set<BuildTarget>([\n 'cloudflare',\n 'vercel',\n 'node',\n 'static',\n 'bun',\n])\n\nexport function targetRendersDocument(target: BuildTarget): boolean {\n return RENDERS_DOCUMENT.has(target)\n}\n\nexport const VALID_TARGETS: BuildTarget[] = [\n 'node',\n 'vercel',\n 'cloudflare',\n 'static',\n 'bun',\n 'deno-deploy',\n 'netlify',\n 'aws-lambda',\n 'theo-cloud',\n]\n"],"mappings":";;;;AA8SA,IAAM,mBAA6C,oBAAI,IAAiB;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,sBAAsB,QAA8B;AAClE,SAAO,iBAAiB,IAAI,MAAM;AACpC;AAEO,IAAM,gBAA+B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -11,42 +11,45 @@ import { extname, join, relative } from "node:path";
11
11
 
12
12
  // src/server/scan/detect-agent-policy.ts
13
13
  import { createRequire } from "node:module";
14
- var require_ = createRequire(import.meta.url);
15
- var ts = require_("typescript");
14
+ var compiler;
15
+ function ts_() {
16
+ compiler ??= createRequire(import.meta.url)("typescript");
17
+ return compiler;
18
+ }
16
19
  var POLICY_EXPORT = "policy";
17
20
  function hasExportModifier(modifiers) {
18
21
  if (!modifiers) return false;
19
22
  for (const m of modifiers) {
20
- if (m.kind === ts.SyntaxKind.ExportKeyword) return true;
23
+ if (m.kind === ts_().SyntaxKind.ExportKeyword) return true;
21
24
  }
22
25
  return false;
23
26
  }
24
27
  function variableStatementDeclaresPolicy(stmt) {
25
- if (!hasExportModifier(ts.getModifiers(stmt))) return false;
28
+ if (!hasExportModifier(ts_().getModifiers(stmt))) return false;
26
29
  return stmt.declarationList.declarations.some(
27
- (decl) => ts.isIdentifier(decl.name) && decl.name.text === POLICY_EXPORT
30
+ (decl) => ts_().isIdentifier(decl.name) && decl.name.text === POLICY_EXPORT
28
31
  );
29
32
  }
30
33
  function exportDeclarationDeclaresPolicy(stmt) {
31
- if (!stmt.exportClause || !ts.isNamedExports(stmt.exportClause)) return false;
34
+ if (!stmt.exportClause || !ts_().isNamedExports(stmt.exportClause)) return false;
32
35
  return stmt.exportClause.elements.some((spec) => spec.name.text === POLICY_EXPORT);
33
36
  }
34
37
  function functionDeclarationDeclaresPolicy(stmt) {
35
- return hasExportModifier(ts.getModifiers(stmt)) && stmt.name?.text === POLICY_EXPORT;
38
+ return hasExportModifier(ts_().getModifiers(stmt)) && stmt.name?.text === POLICY_EXPORT;
36
39
  }
37
40
  function declaresAgentPolicy(filePath, content) {
38
- const sourceFile = ts.createSourceFile(
41
+ const sourceFile = ts_().createSourceFile(
39
42
  filePath,
40
43
  content,
41
- ts.ScriptTarget.Latest,
44
+ ts_().ScriptTarget.Latest,
42
45
  /* setParentNodes */
43
46
  false,
44
- ts.ScriptKind.TS
47
+ ts_().ScriptKind.TS
45
48
  );
46
49
  for (const stmt of sourceFile.statements) {
47
- if (ts.isVariableStatement(stmt) && variableStatementDeclaresPolicy(stmt)) return true;
48
- if (ts.isExportDeclaration(stmt) && exportDeclarationDeclaresPolicy(stmt)) return true;
49
- if (ts.isFunctionDeclaration(stmt) && functionDeclarationDeclaresPolicy(stmt)) return true;
50
+ if (ts_().isVariableStatement(stmt) && variableStatementDeclaresPolicy(stmt)) return true;
51
+ if (ts_().isExportDeclaration(stmt) && exportDeclarationDeclaresPolicy(stmt)) return true;
52
+ if (ts_().isFunctionDeclaration(stmt) && functionDeclarationDeclaresPolicy(stmt)) return true;
50
53
  }
51
54
  return false;
52
55
  }
@@ -281,4 +284,4 @@ export {
281
284
  createFileStampCache,
282
285
  scanAgents
283
286
  };
284
- //# sourceMappingURL=chunk-FHQP23Z5.js.map
287
+ //# sourceMappingURL=chunk-6R5F5YF2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/agent-scan.ts","../src/server/scan/detect-agent-policy.ts","../src/server/scan/errors.ts","../src/server/scan/file-stamp-cache.ts"],"mappings":"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","names":["statSync","statSync"]}
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/adapters/deployed-baked-routes.ts
5
+ function renderBakedRoutes(routes) {
6
+ const routeVar = (index) => `__theoRoute${String(index)}`;
7
+ return {
8
+ routeImports: routes.map(
9
+ (route, index) => `import * as ${routeVar(index)} from '../../${route.filePath}'`
10
+ ),
11
+ routeModuleEntries: routes.map(
12
+ (route, index) => ` ${JSON.stringify(route.filePath)}: ${routeVar(index)},`
13
+ ),
14
+ routeTableEntries: routes.map(
15
+ (route) => ` { filePath: ${JSON.stringify(route.filePath)}, routePath: ${JSON.stringify(route.routePath)}, methods: ${JSON.stringify([...route.methods ?? []])}, ...compilePattern(${JSON.stringify(route.routePath)}) },`
16
+ )
17
+ };
18
+ }
19
+ function routeRuntimeLines(moduleEntries, tableEntries, target) {
20
+ return [
21
+ `// #369 / B-319 \u2014 the routes are baked at build time. This entry used to reach for`,
22
+ `// \`scanServerRoutes\` on the server directory \u2014 a readdirSync, on a platform that uploads`,
23
+ `// a bundle and no source tree. The pattern is recompiled here from the same routePath the`,
24
+ `// scanner used, so one function decides precedence on every target.`,
25
+ `const ROUTE_MODULES = {`,
26
+ ...moduleEntries,
27
+ `}`,
28
+ ``,
29
+ `const routes = [`,
30
+ ...tableEntries,
31
+ `]`,
32
+ ``,
33
+ `// The executor asks for a module by the path the table names. Anything else was`,
34
+ `// never bundled, and saying so beats returning undefined and failing later on a`,
35
+ `// property access far from the cause.`,
36
+ `async function loadModule(path) {`,
37
+ ` const mod = ROUTE_MODULES[path]`,
38
+ ` if (mod === undefined) {`,
39
+ // No backticks in this message: it is emitted INTO a template literal, and a stray one closes
40
+ // it. That is how #344 shipped a SyntaxError, and the emitted-entry parse gate caught it.
41
+ " throw new Error(`Route module '${path}' was not bundled into this deployment. This target uploads only what the build bundled, so every route is imported at build time. Re-run: theokit build --target " + target + "`)",
42
+ ` }`,
43
+ ` return mod`,
44
+ `}`,
45
+ ``
46
+ ];
47
+ }
48
+
49
+ export {
50
+ renderBakedRoutes,
51
+ routeRuntimeLines
52
+ };
53
+ //# sourceMappingURL=chunk-72RSCKMW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/deployed-baked-routes.ts"],"sourcesContent":["/** One route as the build scanner found it, and as both emitters below read it. */\nexport interface BakedRoute {\n readonly filePath: string\n readonly routePath: string\n readonly methods?: readonly string[]\n}\n\n/**\n * The static imports, module map and route table a target gets INSTEAD of a directory scan.\n *\n * ## Why baked at all\n *\n * A platform that uploads a bundle and nothing else has no directory to read. `scanServerRoutes` is\n * a `readdirSync`, so on such a target it finds nothing and every route answers 404 — measured on\n * Cloudflare (#369) and again on Vercel (B-319), where `.vercel/output/functions/api.func/` holds\n * `index.mjs`, `.vc-config.json` and `assets/`, and no `src/`.\n *\n * The imports are STATIC because that is the only kind a bundler follows. A dynamic `import(path)`\n * built from a string is precisely what the platform cannot resolve, and it is what the runtime\n * loader did.\n *\n * ## Why shared rather than copied per adapter\n *\n * Two adapters need this and a third may. B-315 is what copies of one call look like after a while:\n * the same option threaded correctly by two adapters and forgotten by three, which cost a live\n * deploy per target to find. One emitter; the target only supplies what its own error message says.\n *\n * `../../` is the depth both callers write their entry at — `.theokit/cloudflare/worker.mjs` and\n * `.theokit/vercel/entry.mjs` — while `filePath` is relative to the project root.\n */\nexport function renderBakedRoutes(routes: readonly BakedRoute[]): {\n routeImports: string[]\n routeModuleEntries: string[]\n routeTableEntries: string[]\n} {\n const routeVar = (index: number): string => `__theoRoute${String(index)}`\n return {\n routeImports: routes.map(\n (route, index) => `import * as ${routeVar(index)} from '../../${route.filePath}'`,\n ),\n routeModuleEntries: routes.map(\n (route, index) => ` ${JSON.stringify(route.filePath)}: ${routeVar(index)},`,\n ),\n routeTableEntries: routes.map(\n (route) =>\n ` { filePath: ${JSON.stringify(route.filePath)}, routePath: ${JSON.stringify(route.routePath)}, ` +\n `methods: ${JSON.stringify([...(route.methods ?? [])])}, ` +\n `...compilePattern(${JSON.stringify(route.routePath)}) },`,\n ),\n }\n}\n\n/**\n * The route-resolution runtime, as lines of the emitted entry.\n *\n * A module map, the literal table, and a loader that refuses anything the build did not bake —\n * fail-clear per `rules/error-handling.md`, because returning `undefined` here surfaces as a\n * property access far from the cause.\n *\n * `target` appears only in that refusal, so the message names the build command that fixes it. A\n * Vercel operator told to re-run `--target cloudflare` has been sent to the wrong place.\n */\nexport function routeRuntimeLines(\n moduleEntries: string[],\n tableEntries: string[],\n target: string,\n): string[] {\n return [\n `// #369 / B-319 — the routes are baked at build time. This entry used to reach for`,\n `// \\`scanServerRoutes\\` on the server directory — a readdirSync, on a platform that uploads`,\n `// a bundle and no source tree. The pattern is recompiled here from the same routePath the`,\n `// scanner used, so one function decides precedence on every target.`,\n `const ROUTE_MODULES = {`,\n ...moduleEntries,\n `}`,\n ``,\n `const routes = [`,\n ...tableEntries,\n `]`,\n ``,\n `// The executor asks for a module by the path the table names. Anything else was`,\n `// never bundled, and saying so beats returning undefined and failing later on a`,\n `// property access far from the cause.`,\n `async function loadModule(path) {`,\n ` const mod = ROUTE_MODULES[path]`,\n ` if (mod === undefined) {`,\n // No backticks in this message: it is emitted INTO a template literal, and a stray one closes\n // it. That is how #344 shipped a SyntaxError, and the emitted-entry parse gate caught it.\n \" throw new Error(`Route module '\" +\n '${path}' +\n \"' was not bundled into this deployment. \" +\n 'This target uploads only what the build bundled, so every route is imported at build time. ' +\n 'Re-run: theokit build --target ' +\n target +\n '`)',\n ` }`,\n ` return mod`,\n `}`,\n ``,\n ]\n}\n"],"mappings":";;;;AA8BO,SAAS,kBAAkB,QAIhC;AACA,QAAM,WAAW,CAAC,UAA0B,cAAc,OAAO,KAAK,CAAC;AACvE,SAAO;AAAA,IACL,cAAc,OAAO;AAAA,MACnB,CAAC,OAAO,UAAU,eAAe,SAAS,KAAK,CAAC,gBAAgB,MAAM,QAAQ;AAAA,IAChF;AAAA,IACA,oBAAoB,OAAO;AAAA,MACzB,CAAC,OAAO,UAAU,KAAK,KAAK,UAAU,MAAM,QAAQ,CAAC,KAAK,SAAS,KAAK,CAAC;AAAA,IAC3E;AAAA,IACA,mBAAmB,OAAO;AAAA,MACxB,CAAC,UACC,iBAAiB,KAAK,UAAU,MAAM,QAAQ,CAAC,gBAAgB,KAAK,UAAU,MAAM,SAAS,CAAC,cAClF,KAAK,UAAU,CAAC,GAAI,MAAM,WAAW,CAAC,CAAE,CAAC,CAAC,uBACjC,KAAK,UAAU,MAAM,SAAS,CAAC;AAAA,IACxD;AAAA,EACF;AACF;AAYO,SAAS,kBACd,eACA,cACA,QACU;AACV,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA,+MAKE,SACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -49,4 +49,4 @@ function nameOf(entry) {
49
49
  export {
50
50
  planDeployedPlugins
51
51
  };
52
- //# sourceMappingURL=chunk-V5SSTT3C.js.map
52
+ //# sourceMappingURL=chunk-C43AHOHI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/deployed-plugins-module.ts"],"sourcesContent":["/**\n * The plugins module a build writes beside a deployed entry (usetheokit/theokit#425).\n *\n * ## What it is for\n *\n * `config.plugins` used to reach no deployed target at all, so `onRequest`, `preHandler`,\n * `onResponse` and `onError` were dead on every Web-standards deployment while firing locally. The\n * reason was never the hook loop — `executeRoute` has always run it — but that a constructed\n * plugin closes over state, and there is no literal for a closure.\n *\n * A plugin declared as a module SPECIFIER can be carried, because the build can emit a static\n * import for it. That is the road `renderBakedRoutes` already takes for route modules (#369):\n * resolve on the build machine, emit a static specifier, let the target's bundler see through it.\n *\n * ## Why the specifier is `../../`\n *\n * Every target that can carry plugins writes its entry two levels below the project root —\n * `.theokit/cloudflare/`, `.theokit/bun/`, `.theokit/deno/` — and a declared specifier is relative\n * to that root. The same arithmetic `renderBakedRoutes` does for routes, for the same reason.\n *\n * ## Which targets can carry them at all\n *\n * Only the ones whose output is BUNDLED from the project: Cloudflare (wrangler), Bun and Deno all\n * resolve the emitted import against the app's own source tree. Vercel, Netlify and AWS Lambda\n * receive a standalone function directory and never see the app's modules, so for them a named\n * refusal — which `findUnappliedConfig` already prints — remains the honest answer, and this module\n * is not called.\n */\n\n/**\n * A plugin the build was asked to carry and cannot.\n *\n * Its own type because the alternative is the defect this whole issue is: hooks that do not fire\n * and nothing that says so. A target that CAN carry a named module but was handed a closure has\n * something actionable to say, so it says it at build time rather than at the first request\n * (`docs/program/three-target-parity.md` § 3 — refuse by name).\n */\nexport class UnbakeablePluginError extends Error {\n constructor(index: number, name: string, target: string) {\n super(\n `plugins[${index}] (\"${name}\") cannot be carried to the ${target} target: it is a constructed ` +\n `plugin, and a closure has no literal a generated entry could hold. Declare it by module ` +\n `specifier instead — plugins: ['./src/plugins/${name}.ts'] — so the build can emit a ` +\n `static import for it. Both forms work locally; only the specifier survives a deploy.`,\n )\n this.name = 'UnbakeablePluginError'\n }\n}\n\nexport interface DeployedPluginsPlan {\n /** The module source to write beside the entry. */\n readonly source: string\n /** What the entry imports it as — a sibling, because both are written to the same directory. */\n readonly moduleSpecifier: string\n /** The declared specifiers, in order, for the build to report. */\n readonly specifiers: readonly string[]\n}\n\nconst MODULE_FILENAME = 'theo.plugins.mjs'\n\n/**\n * Plan the plugins module for a target that can carry one.\n *\n * @param plugins - the raw `config.plugins` array.\n * @param target - the deploy target, named in the refusal so the message says where it applies.\n * @returns `undefined` when the app declares no plugins, so an app carrying none emits nothing.\n * @throws UnbakeablePluginError when an entry is a constructed plugin rather than a specifier.\n */\nexport function planDeployedPlugins(\n plugins: readonly unknown[] | undefined,\n target: string,\n): DeployedPluginsPlan | undefined {\n if (plugins === undefined || plugins.length === 0) return undefined\n\n const specifiers = plugins.map((entry, index) => {\n if (typeof entry === 'string') return entry\n throw new UnbakeablePluginError(index, nameOf(entry), target)\n })\n\n const vars = specifiers.map((_, i) => `__theoPlugin${String(i)}`)\n\n return {\n moduleSpecifier: `./${MODULE_FILENAME}`,\n specifiers,\n source: [\n `// Generated by Theo — the plugins the deployed entry registers (usetheokit/theokit#425).`,\n `//`,\n `// Static imports, resolved on the build machine. A constructed plugin has no literal, which`,\n `// is why the config declares these by module specifier and why this file can exist at all.`,\n ...specifiers.map((s, i) => `import ${vars[i]} from '${entrySpecifier(s)}'`),\n ``,\n `// Order is DECLARED order, because plugin order is hook order.`,\n `export default { plugins: [${vars.join(', ')}] }`,\n ``,\n ].join('\\n'),\n }\n}\n\n/**\n * How the emitted module names a declared specifier.\n *\n * A RELATIVE specifier is relative to the project root, and the module sits two levels below it, so\n * it is re-based — the arithmetic `renderBakedRoutes` does for routes. A BARE specifier is a package\n * and must be left alone: `../../my-plugin-pkg` is a path that does not exist, where\n * `my-plugin-pkg` is a dependency the target's bundler resolves from `node_modules` like any other.\n */\nfunction entrySpecifier(specifier: string): string {\n if (!specifier.startsWith('.')) return specifier\n return `../../${specifier.replace(/^\\.\\/+/, '')}`\n}\n\n/** The plugin's own name when it has one, so the refusal names the thing the author wrote. */\nfunction nameOf(entry: unknown): string {\n if (entry != null && typeof entry === 'object') {\n const named = (entry as { name?: unknown }).name\n if (typeof named === 'string' && named.length > 0) return named\n }\n return 'unnamed'\n}\n"],"mappings":";;;;AAqCO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YAAY,OAAe,MAAc,QAAgB;AACvD;AAAA,MACE,WAAW,KAAK,OAAO,IAAI,+BAA+B,MAAM,0KAEd,IAAI;AAAA,IAExD;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAWA,IAAM,kBAAkB;AAUjB,SAAS,oBACd,SACA,QACiC;AACjC,MAAI,YAAY,UAAa,QAAQ,WAAW,EAAG,QAAO;AAE1D,QAAM,aAAa,QAAQ,IAAI,CAAC,OAAO,UAAU;AAC/C,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,IAAI,sBAAsB,OAAO,OAAO,KAAK,GAAG,MAAM;AAAA,EAC9D,CAAC;AAED,QAAM,OAAO,WAAW,IAAI,CAAC,GAAG,MAAM,eAAe,OAAO,CAAC,CAAC,EAAE;AAEhE,SAAO;AAAA,IACL,iBAAiB,KAAK,eAAe;AAAA,IACrC;AAAA,IACA,QAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG,WAAW,IAAI,CAAC,GAAG,MAAM,UAAU,KAAK,CAAC,CAAC,UAAU,eAAe,CAAC,CAAC,GAAG;AAAA,MAC3E;AAAA,MACA;AAAA,MACA,8BAA8B,KAAK,KAAK,IAAI,CAAC;AAAA,MAC7C;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;AAUA,SAAS,eAAe,WAA2B;AACjD,MAAI,CAAC,UAAU,WAAW,GAAG,EAAG,QAAO;AACvC,SAAO,SAAS,UAAU,QAAQ,UAAU,EAAE,CAAC;AACjD;AAGA,SAAS,OAAO,OAAwB;AACtC,MAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;AAC9C,UAAM,QAAS,MAA6B;AAC5C,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAAA,EAC5D;AACA,SAAO;AACT;","names":[]}
@@ -27,9 +27,10 @@ function deployedAgentsFragment(source, host = {}) {
27
27
  if (source === void 0) return EMPTY;
28
28
  if (source.kind === "baked" && source.agents.length === 0) return EMPTY;
29
29
  const pathname = host.pathname ?? "url.pathname";
30
+ const baseUrl = host.baseUrl ?? "url.origin";
30
31
  const notFound = host.notFound ?? "notFoundResponse()";
31
32
  const prefix = host.importPrefix ?? "";
32
- const resolution = source.kind === "baked" ? bakedResolution(source.agents, source.contextModule, host.pluginRunnerExpr) : scannedResolution({ ...source, pluginRunnerExpr: host.pluginRunnerExpr });
33
+ const resolution = source.kind === "baked" ? bakedResolution(source.agents, source.contextModule, host.pluginRunnerExpr, baseUrl) : scannedResolution({ ...source, pluginRunnerExpr: host.pluginRunnerExpr, baseUrl });
33
34
  return {
34
35
  hostBypass: ` && !__theoIsAgentCardPath(${pathname})`,
35
36
  imports: [
@@ -123,7 +124,7 @@ function deployedAgentsFragment(source, host = {}) {
123
124
  ]
124
125
  };
125
126
  }
126
- function bakedResolution(agents, contextModule, pluginRunnerExpr) {
127
+ function bakedResolution(agents, contextModule, pluginRunnerExpr, baseUrl) {
127
128
  const varOf = (index) => `__theoAgent${String(index)}`;
128
129
  return {
129
130
  imports: [
@@ -214,7 +215,7 @@ function bakedResolution(agents, contextModule, pluginRunnerExpr) {
214
215
  ` const node = agentNodes.find((a) => a.filePath === filePath)`,
215
216
  ` return node === undefined ? undefined : agents[node.name]`,
216
217
  ` },`,
217
- ` baseUrl: url.origin,`,
218
+ ` baseUrl: ${baseUrl},`,
218
219
  ...AUX_DEPS_PARITY,
219
220
  ` }`
220
221
  ]
@@ -257,7 +258,7 @@ function scannedResolution(source) {
257
258
  ` const auxDeps = {`,
258
259
  ` agents: agentsCache,`,
259
260
  ` loadModule: ${source.loadModule},`,
260
- ` baseUrl: url.origin,`,
261
+ ` baseUrl: ${source.baseUrl},`,
261
262
  ...AUX_DEPS_PARITY,
262
263
  ` }`
263
264
  ]
@@ -674,7 +675,7 @@ function deployedRuntimeConfigFragment(options) {
674
675
  const spread = [];
675
676
  if (pluginsModule !== void 0) {
676
677
  imports.push(
677
- `import { createPluginRunnerFromConfig } from 'theokit/server'`,
678
+ `import { createPluginRunnerFromConfig } from 'theokit/server/plugins'`,
678
679
  `// #425 \u2014 the app's own plugins, resolved on the build machine and written beside this entry.`,
679
680
  `// A closure has no literal, so this is an import rather than a baked value.`,
680
681
  `import theoRuntimeConfig from '${pluginsModule}'`
@@ -687,7 +688,7 @@ function deployedRuntimeConfigFragment(options) {
687
688
  spread.push(`pluginRunner: await THEO_PLUGIN_RUNNER`);
688
689
  }
689
690
  if (serialization !== void 0) {
690
- imports.push(`import { resolveTransformer } from 'theokit/server'`);
691
+ imports.push(`import { resolveTransformer } from 'theokit/server/http'`);
691
692
  declarations.push(
692
693
  `// #425 \u2014 a literal, because \`config.serialization\` is a SELECTOR and not a transformer.`,
693
694
  `// Same string, same function \`theokit start\` calls, so the deployed response and the local`,
@@ -727,4 +728,4 @@ export {
727
728
  deployedRuntimeConfigFragment,
728
729
  deployedTraceFragment
729
730
  };
730
- //# sourceMappingURL=chunk-C5CAL3DX.js.map
731
+ //# sourceMappingURL=chunk-EQJF2EH5.js.map