theokit 0.62.1 → 0.63.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 (35) hide show
  1. package/dist/{aws-lambda-2R77AV5F.js → aws-lambda-AN6CEBXO.js} +5 -4
  2. package/dist/aws-lambda-AN6CEBXO.js.map +1 -0
  3. package/dist/{build-CZAM7K6J.js → build-RF2NLRHS.js} +2 -2
  4. package/dist/{bun-DJ6QEFIP.js → bun-3FFL7CS4.js} +5 -4
  5. package/dist/bun-3FFL7CS4.js.map +1 -0
  6. package/dist/{chunk-KPZ2JSAU.js → chunk-EW5IG7GA.js} +101 -38
  7. package/dist/chunk-EW5IG7GA.js.map +1 -0
  8. package/dist/{chunk-VPX3MS5D.js → chunk-PALZUV6E.js} +5 -1
  9. package/dist/chunk-PALZUV6E.js.map +1 -0
  10. package/dist/cli/index.js +2 -2
  11. package/dist/{cloudflare-UYKOO6EP.js → cloudflare-VIQX4FVC.js} +5 -4
  12. package/dist/cloudflare-VIQX4FVC.js.map +1 -0
  13. package/dist/{deno-deploy-W2JWCYPT.js → deno-deploy-PTO7IE4G.js} +2 -2
  14. package/dist/{netlify-BJQGPMGY.js → netlify-3VSNVURQ.js} +2 -2
  15. package/dist/{preview-3TLURUAL.js → preview-F6SR2JAQ.js} +2 -2
  16. package/dist/{registry-PAJOMMVV.js → registry-RZHBBLIF.js} +7 -7
  17. package/dist/server/index.d.ts +1 -1
  18. package/dist/server/index.js +3 -1
  19. package/dist/server/index.js.map +1 -1
  20. package/dist/server/webhook/index.d.ts +46 -1
  21. package/dist/server/webhook/index.js +3 -1
  22. package/dist/{vercel-BEDGUVXX.js → vercel-BZ35DF3B.js} +5 -4
  23. package/dist/vercel-BZ35DF3B.js.map +1 -0
  24. package/package.json +3 -3
  25. package/dist/aws-lambda-2R77AV5F.js.map +0 -1
  26. package/dist/bun-DJ6QEFIP.js.map +0 -1
  27. package/dist/chunk-KPZ2JSAU.js.map +0 -1
  28. package/dist/chunk-VPX3MS5D.js.map +0 -1
  29. package/dist/cloudflare-UYKOO6EP.js.map +0 -1
  30. package/dist/vercel-BEDGUVXX.js.map +0 -1
  31. /package/dist/{build-CZAM7K6J.js.map → build-RF2NLRHS.js.map} +0 -0
  32. /package/dist/{deno-deploy-W2JWCYPT.js.map → deno-deploy-PTO7IE4G.js.map} +0 -0
  33. /package/dist/{netlify-BJQGPMGY.js.map → netlify-3VSNVURQ.js.map} +0 -0
  34. /package/dist/{preview-3TLURUAL.js.map → preview-F6SR2JAQ.js.map} +0 -0
  35. /package/dist/{registry-PAJOMMVV.js.map → registry-RZHBBLIF.js.map} +0 -0
@@ -6,8 +6,9 @@ import {
6
6
  deployedRuntimeConfigFragment,
7
7
  deployedTraceFragment,
8
8
  describeDeployedSecurityHeaders,
9
- renderSecurityHeadersConfigLiteral
10
- } from "./chunk-VPX3MS5D.js";
9
+ renderSecurityHeadersConfigLiteral,
10
+ serverDirLiteral
11
+ } from "./chunk-PALZUV6E.js";
11
12
  import {
12
13
  nodeAdapter
13
14
  } from "./chunk-KI4DOTN4.js";
@@ -156,7 +157,7 @@ function renderAwsLambdaEntry(opts = {}) {
156
157
  `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,
157
158
  ``,
158
159
  `const cwd = process.cwd()`,
159
- `const serverDir = resolve(cwd, 'server')`,
160
+ `const serverDir = resolve(cwd, ${serverDirLiteral(opts)})`,
160
161
  `let routesCache = null`,
161
162
  `let loaderCache = null`,
162
163
  ``,
@@ -258,4 +259,4 @@ export {
258
259
  renderAwsLambdaEntry,
259
260
  responseToLambdaResultV2
260
261
  };
261
- //# sourceMappingURL=aws-lambda-2R77AV5F.js.map
262
+ //# sourceMappingURL=aws-lambda-AN6CEBXO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/aws-lambda.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * AWS Lambda adapter. Writes to `cwd/.theokit/lambda/`. Build-time tool.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { deployedCorsFragment, type DeployedCorsOptions } from './deployed-cors.js'\nimport { deployedCsrfFragment, type DeployedCsrfOptions } from './deployed-csrf.js'\nimport {\n deployedRuntimeConfigFragment,\n serverDirLiteral,\n type DeployedRuntimeConfigOptions,\n type DeployedServerDirOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport {\n describeDeployedSecurityHeaders,\n renderSecurityHeadersConfigLiteral,\n} from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport interface AwsLambdaBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeEntry?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n}\n\nexport interface RequestShape {\n method: string\n path: string\n headers: Record<string, string>\n body?: string\n}\n\nexport interface LambdaResultV2 {\n statusCode: number\n headers: Record<string, string>\n body: string\n isBase64Encoded: boolean\n}\n\nconst BINARY_CONTENT_TYPES = [\n 'application/octet-stream',\n 'application/pdf',\n 'application/zip',\n 'image/',\n 'audio/',\n 'video/',\n]\n\nfunction isBinaryContentType(contentType: string | undefined): boolean {\n if (!contentType) return false\n const ct = contentType.toLowerCase()\n return BINARY_CONTENT_TYPES.some((prefix) => ct.startsWith(prefix))\n}\n\ninterface APIGatewayProxyEventV2 {\n version: string\n requestContext: {\n http: { method: string; path: string }\n }\n headers?: Record<string, string | undefined>\n body?: string\n isBase64Encoded?: boolean\n}\n\nexport function eventV2ToRequestShape(event: APIGatewayProxyEventV2): RequestShape {\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(event.headers ?? {})) {\n if (v !== undefined) headers[k] = v\n }\n let body: string | undefined = event.body\n if (body !== undefined && event.isBase64Encoded) {\n body = Buffer.from(body, 'base64').toString('utf-8')\n }\n return {\n method: event.requestContext.http.method,\n path: event.requestContext.http.path,\n headers,\n body,\n }\n}\n\nexport function responseToLambdaResultV2(\n statusCode: number,\n headers: Record<string, string>,\n body: string,\n): LambdaResultV2 {\n const isBinary = isBinaryContentType(headers['content-type'])\n return {\n statusCode,\n headers,\n body: isBinary ? Buffer.from(body, 'binary').toString('base64') : body,\n isBase64Encoded: isBinary,\n }\n}\n\n// Generated-code fragments — extracted so the parent emitter stays under the\n// max-lines-per-function ceiling.\n/** The v2 result conversion, the handler, and the routing that feeds them. */\nfunction awsLambdaHandlerFragment(runtimeSpread: string): string[] {\n return [\n `// #382 — this target is DELISTED for response streaming, by construction.`,\n `// The Lambda v2 result object below carries \\`body\\` as a string, so the`,\n `// response cannot exist until the run has finished, no matter how the`,\n `// framework's shim writes it. Streaming here would require`,\n `// \\`awslambda.streamifyResponse\\` plus a Function URL in RESPONSE_STREAM`,\n `// invoke mode — neither of which this adapter emits, and which would break`,\n `// every API Gateway deployment of it. Rather than degrade in silence, a`,\n `// response that is unambiguously a stream is named in the logs below.`,\n `async function responseToV2Result(response, routePath) {`,\n ` const headers = {}`,\n ` response.headers.forEach((v, k) => { headers[k] = v })`,\n ` const ct = headers['content-type'] ?? ''`,\n ` if (ct.includes('text/event-stream')) {`,\n ` console.warn(`,\n ` '[theo][aws-lambda] ' + routePath + ' returned a streaming response (' + ct + '), ' +`,\n ` 'but the aws-lambda target buffers it whole and delivers it when the run ends. ' +`,\n ` 'This target is delisted for streaming (theokit#382). Deploy streaming routes to ' +`,\n ` 'cloudflare, vercel, netlify, bun, deno-deploy or node.'`,\n ` )`,\n ` }`,\n ` const isBinary = /^application\\\\/(octet-stream|pdf|zip)|^(image|audio|video)\\\\//.test(ct)`,\n ` if (isBinary) {`,\n ` const buf = Buffer.from(await response.arrayBuffer())`,\n ` return {`,\n ` statusCode: response.status,`,\n ` headers,`,\n ` body: buf.toString('base64'),`,\n ` isBase64Encoded: true,`,\n ` }`,\n ` }`,\n ` return {`,\n ` statusCode: response.status,`,\n ` headers,`,\n ` body: await response.text(),`,\n ` isBase64Encoded: false,`,\n ` }`,\n `}`,\n ``,\n `// Every outcome — including both 404s — is produced as a Web Response by`,\n `// routeRequest, so the security baseline is applied at ONE place and the`,\n `// v2 result object is built from headers that already carry it.`,\n `export const handler = async (event) => {`,\n ` const path = event.requestContext?.http?.path ?? '/'`,\n ` // #409 — the same event, read as a Web Request so the CORS matcher can see the method and`,\n ` // the \\`origin\\` header. \\`eventV2ToRequest\\` reads a body already present in the event, so`,\n ` // unlike the Node targets there is no stream to drain twice.`,\n ` const corsRequest = eventV2ToRequest(event)`,\n ``,\n ` // The preflight is answered BEFORE anything routes: an OPTIONS the router handles is an`,\n ` // OPTIONS the browser never gets a CORS answer to.`,\n ` const preflight = corsPreflight(corsRequest)`,\n ` const response = preflight !== null`,\n ` ? withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ` : withCors(corsRequest, withSecurityHeaders(await routeRequest(event, path), SECURITY_HEADERS))`,\n ` return responseToV2Result(response, path)`,\n `}`,\n ``,\n `async function routeRequest(event, path) {`,\n ` if (!path.startsWith('/api/')) {`,\n ` return new Response(`,\n ` 'Static assets must be served from CloudFront or a separate static host.',`,\n ` { status: 404, headers: { 'content-type': 'text/plain' } },`,\n ` )`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ` const match = matchRoute(path, routesCache)`,\n ` if (!match) {`,\n ` return new Response(JSON.stringify({ error: { code: 'NOT_FOUND' } }), {`,\n ` status: 404,`,\n ` headers: { 'content-type': 'application/json' },`,\n ` })`,\n ` }`,\n ``,\n ` const request = eventV2ToRequest(event)`,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId, ...CSRF_CONFIG, ${runtimeSpread} }))`,\n `}`,\n ]\n}\n\nexport function renderAwsLambdaEntry(\n opts: { securityHeaders?: SecurityHeadersConfig } & DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedServerDirOptions &\n DeployedCorsOptions = {},\n): string {\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n return [\n `// Generated by Theo — AWS Lambda Adapter`,\n `// Use with API Gateway HTTP API v2 (default).`,\n ``,\n `import { resolve } from 'node:path'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, ${serverDirLiteral(opts)})`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed function has no`,\n `// theo.config.ts to read. Non-API paths 404 here and the document is served`,\n `// from CloudFront or another static host, so this covers the API and not the`,\n `// page (usetheokit/theokit#412).`,\n `const SECURITY_HEADERS_CONFIG = ${renderSecurityHeadersConfigLiteral(opts.securityHeaders)}`,\n `const SECURITY_HEADERS = buildSecurityHeaders(SECURITY_HEADERS_CONFIG, { production: true })`,\n ``,\n ...runtimeConfig.imports,\n ...runtimeConfig.declarations,\n ...deployedCsrfFragment(opts),\n ``,\n ...deployedCorsFragment(opts.cors, 'aws-lambda'),\n ``,\n `function eventV2ToRequest(event) {`,\n ` const method = event.requestContext?.http?.method ?? 'GET'`,\n ` const path = event.requestContext?.http?.path ?? '/'`,\n ` const host = event.headers?.host ?? 'lambda'`,\n ` const qs = event.rawQueryString ? '?' + event.rawQueryString : ''`,\n ` const url = 'https://' + host + path + qs`,\n ` const headers = new Headers()`,\n ` for (const [k, v] of Object.entries(event.headers ?? {})) {`,\n ` if (v !== undefined) headers.set(k, String(v))`,\n ` }`,\n ` let body = event.body`,\n ` if (body && event.isBase64Encoded) {`,\n ` body = Buffer.from(body, 'base64')`,\n ` }`,\n ` return new Request(url, {`,\n ` method,`,\n ` headers,`,\n ` body: method === 'GET' || method === 'HEAD' ? undefined : body,`,\n ` })`,\n `}`,\n ``,\n ...awsLambdaHandlerFragment(runtimeConfig.executeRouteSpread),\n ].join('\\n')\n}\n\nexport async function buildAwsLambda(\n config: TheoConfig,\n cwd: string,\n deps: AwsLambdaBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('aws-lambda', readManifest(cwd))\n\n // #382 — refuse by name rather than build something that cannot do what was\n // asked. `ssrStreaming` is the config's declaration that responses stream,\n // and this target is delisted for that (see `awsLambdaAdapter.streamsResponses`).\n if (config.ssrStreaming) {\n throw new Error(\n '[adapter-aws-lambda] ssrStreaming is on, but the aws-lambda target does not stream ' +\n 'responses: the Lambda v2 result object carries the body as a string, so nothing can ' +\n 'leave the function before the run ends. Response streaming would need ' +\n 'awslambda.streamifyResponse plus a Function URL in RESPONSE_STREAM invoke mode, which ' +\n 'this adapter does not emit. Build a streaming target instead (cloudflare, vercel, ' +\n 'netlify, bun, deno-deploy, node), or set ssrStreaming: false in theo.config.ts.',\n )\n }\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const outputDir = resolve(cwd, '.theokit/aws')\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n ensureDir(outputDir)\n\n const entry = renderAwsLambdaEntry({\n securityHeaders: config.security?.headers,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n })\n const write =\n deps.writeEntry ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n write(resolve(outputDir, 'handler.mjs'), entry)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ AWS Lambda output → .theokit/aws/handler.mjs')\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'aws-lambda',\n securityHeaders: config.security?.headers,\n mintsNonce: false,\n documentHeaders: 'platform-unmanaged',\n })}\\n`,\n )\n}\n\nexport const awsLambdaAdapter: DeployAdapter = {\n name: 'aws-lambda',\n // #382 — delisted for streaming, deliberately. See DeployAdapter.\n streamsResponses: false,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied -- to every response this function returns.\n // The document comes from CloudFront or another static host and does not pass\n // through it (usetheokit/theokit#412).\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization'],\n build(config, cwd, ctx) {\n return buildAwsLambda(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":[]}
@@ -603,7 +603,7 @@ async function runAdapterBuild(target, config, cwd) {
603
603
  };
604
604
  }
605
605
  };
606
- const { resolveAdapter } = await import("./registry-PAJOMMVV.js");
606
+ const { resolveAdapter } = await import("./registry-RZHBBLIF.js");
607
607
  const adapter = await resolveAdapter(target);
608
608
  const { warnUnappliedConfig, assertRateLimitEnforceable } = await import("./config-support-O4REYSK5.js");
609
609
  assertRateLimitEnforceable(config, adapter, target);
@@ -676,4 +676,4 @@ function relativize3(absPath, root) {
676
676
  export {
677
677
  buildCommand
678
678
  };
679
- //# sourceMappingURL=build-CZAM7K6J.js.map
679
+ //# sourceMappingURL=build-RF2NLRHS.js.map
@@ -10,8 +10,9 @@ import {
10
10
  deployedRuntimeConfigFragment,
11
11
  deployedTraceFragment,
12
12
  describeDeployedSecurityHeaders,
13
- renderSecurityHeadersConfigLiteral
14
- } from "./chunk-VPX3MS5D.js";
13
+ renderSecurityHeadersConfigLiteral,
14
+ serverDirLiteral
15
+ } from "./chunk-PALZUV6E.js";
15
16
  import {
16
17
  nodeAdapter
17
18
  } from "./chunk-KI4DOTN4.js";
@@ -168,7 +169,7 @@ function renderBunEntry(port, opts = {}) {
168
169
  ``,
169
170
  `const cwd = process.cwd()`,
170
171
  `const clientDir = resolve(cwd, '.theokit/client')`,
171
- `const serverDir = resolve(cwd, 'server')`,
172
+ `const serverDir = resolve(cwd, ${serverDirLiteral(opts)})`,
172
173
  `const port = process.env.PORT ? Number(process.env.PORT) : ${port}`,
173
174
  ``,
174
175
  `// #410 \u2014 the security baseline \`theokit start\` puts on every response,`,
@@ -343,4 +344,4 @@ export {
343
344
  bunAdapter,
344
345
  renderBunEntry
345
346
  };
346
- //# sourceMappingURL=bun-DJ6QEFIP.js.map
347
+ //# sourceMappingURL=bun-3FFL7CS4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/bun.ts","../src/adapters/deployed-rate-limit.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Bun deploy adapter. Writes to `cwd/.theokit/bun/`. Build-time.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { deployedAgentsFragment } from './deployed-agents.js'\nimport { deployedCorsFragment, type DeployedCorsOptions } from './deployed-cors.js'\nimport { deployedCsrfFragment, type DeployedCsrfOptions } from './deployed-csrf.js'\nimport { planDeployedPlugins } from './deployed-plugins-module.js'\nimport {\n deployedRateLimitFragment,\n rateLimitCheckFragment,\n type DeployedRateLimitOptions,\n} from './deployed-rate-limit.js'\nimport {\n deployedRuntimeConfigFragment,\n serverDirLiteral,\n type DeployedRuntimeConfigOptions,\n type DeployedServerDirOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport {\n describeDeployedSecurityHeaders,\n renderSecurityHeadersConfigLiteral,\n} from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport interface BunBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeEntry?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n}\n\nexport function renderBunEntry(\n port: number,\n opts: {\n ssrStreaming?: boolean\n securityHeaders?: SecurityHeadersConfig\n } & DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedServerDirOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions = {},\n): string {\n const streamingComment = opts.ssrStreaming\n ? `// T2.3 — ssrStreaming on; renderStreamingWeb may be consumed by app code`\n : `// (ssrStreaming off)`\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n const agentsFragment = deployedAgentsFragment(\n { kind: 'scan', projectRoot: 'cwd', loadModule: 'loadModule' },\n { pathname: 'pathname' },\n )\n return [\n `// Generated by Theo — Bun Adapter`,\n `// Run: bun run .theokit/bun/server.mjs`,\n `// Requires Bun >= 1.1`,\n streamingComment,\n ``,\n `// EC-1: dev-mode guard`,\n `if (process.env.NODE_ENV !== 'production') {`,\n ` console.error('TheoBunAdapter is production-only. Use \\\\'theokit dev\\\\' (Node) for development.')`,\n ` process.exit(1)`,\n `}`,\n ``,\n `// Version check: Bun >= 1.1`,\n `if (typeof Bun === 'undefined') {`,\n ` console.error('TheoBunAdapter must run inside Bun. Got Node.js or unknown runtime.')`,\n ` process.exit(1)`,\n `}`,\n `{`,\n ` const [maj, min] = (Bun.version ?? '0.0').split('.').map(Number)`,\n ` if (maj < 1 || (maj === 1 && min < 1)) {`,\n ` console.error('TheoBunAdapter requires Bun >= 1.1; got ' + Bun.version)`,\n ` process.exit(1)`,\n ` }`,\n `}`,\n ``,\n `import { resolve, join } from 'node:path'`,\n `import { existsSync } from 'node:fs'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler${opts.rateLimit === undefined ? '' : ', createRateLimiterWeb'} } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n `// T3.2 — WS bridge for Bun runtime`,\n `import { createBunWsBridge } from 'theokit/adapters/ws-shim'`,\n `import { scanWebSocketRoutes } from 'theokit/server'`,\n ``,\n `const cwd = process.cwd()`,\n `const clientDir = resolve(cwd, '.theokit/client')`,\n `const serverDir = resolve(cwd, ${serverDirLiteral(opts)})`,\n `const port = process.env.PORT ? Number(process.env.PORT) : ${port}`,\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed process has no`,\n `// theo.config.ts to read. Bun is the one Web target whose own handler serves`,\n `// the HTML document (static file + SPA fallback below), so the document gets`,\n `// these headers too -- with a nonce-less CSP, because that HTML was written`,\n `// at build time and carries no nonce on its script tags (EC-4).`,\n `const SECURITY_HEADERS_CONFIG = ${renderSecurityHeadersConfigLiteral(opts.securityHeaders)}`,\n `const SECURITY_HEADERS = buildSecurityHeaders(SECURITY_HEADERS_CONFIG, { production: true })`,\n ``,\n ...runtimeConfig.imports,\n ...agentsFragment.imports,\n ...runtimeConfig.declarations,\n ...agentsFragment.declarations,\n ...deployedCsrfFragment(opts),\n ``,\n ...deployedCorsFragment(opts.cors, 'bun'),\n ``,\n ...deployedRateLimitFragment(opts.rateLimit, 'bun', 'server.requestIP(request)?.address'),\n ``,\n `const routes = existsSync(serverDir) ? scanServerRoutes(serverDir) : []`,\n `const wsRoutes = existsSync(serverDir) ? scanWebSocketRoutes(serverDir) : []`,\n `const loadModule = createProductionLoader()`,\n ``,\n `// Bun WebSocket config — single handler dispatches to first ws route,`,\n `// or a default no-op when none declared. Per-route routing happens above`,\n `// (the upgrade decision is made in fetch handler before Bun.serve hands off).`,\n `const wsBridge = wsRoutes.length > 0`,\n ` ? createBunWsBridge({`,\n ` onOpen: () => {},`,\n ` onMessage: (ws, data) => { ws.send(data) },`,\n ` onClose: () => {},`,\n ` })`,\n ` : { open: () => {}, message: () => {}, close: () => {} }`,\n ``,\n `function notFoundResponse() {`,\n ` return new Response(JSON.stringify({ error: { code: 'NOT_FOUND', message: 'Route not found' } }), {`,\n ` status: 404,`,\n ` headers: { 'Content-Type': 'application/json' },`,\n ` })`,\n `}`,\n ``,\n `Bun.serve({`,\n ` port,`,\n ` websocket: wsBridge,`,\n ` async fetch(request, server) {`,\n ` // #409 — the preflight is answered BEFORE anything routes: an OPTIONS the router`,\n ` // handles is an OPTIONS the browser never gets a CORS answer to.`,\n ` const preflight = corsPreflight(request)`,\n ` if (preflight !== null) return withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ...rateLimitCheckFragment(opts.rateLimit, ' '),\n ` return withCors(request, withSecurityHeaders(await handleRequest(request), SECURITY_HEADERS))`,\n ` },`,\n `})`,\n ``,\n ...bunHandleRequestFragment(runtimeConfig.executeRouteSpread, agentsFragment.branch),\n ].join('\\n')\n}\n\n/**\n * The request handler, as generated source.\n *\n * Extracted for the reason `vercel.ts` extracts its own fragments: the emitter is one array\n * literal, so every line the entry gains counts against `max-lines-per-function`, and #410 added\n * the CSRF literal to an emitter that was already sitting exactly at the ceiling.\n */\nfunction bunHandleRequestFragment(runtimeSpread: string, agentBranch: readonly string[]): string[] {\n return [\n `async function handleRequest(request) {`,\n ` const url = new URL(request.url)`,\n ` const pathname = url.pathname`,\n ``,\n ` // 1) Static assets`,\n ` const staticPath = pathname === '/' ? '/index.html' : pathname`,\n ` const fullStatic = join(clientDir, staticPath)`,\n ` if (existsSync(fullStatic)) {`,\n ` const file = Bun.file(fullStatic)`,\n ` if (await file.exists()) return new Response(file)`,\n ` }`,\n ``,\n ` // 2) API routes through the full executeRoute pipeline via the shim`,\n ` if (pathname.startsWith('/api/')) {`,\n ...agentBranch,\n ``,\n ` const match = matchRoute(pathname, routes)`,\n ` if (!match) return notFoundResponse()`,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — not awaited: toResponse() settles at the headers and carries`,\n ` // a live body, so Bun.serve streams while the handler writes.`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule, serverDir, requestId, ...CSRF_CONFIG, ${runtimeSpread} }))`,\n ` }`,\n ``,\n ` // 3) SPA fallback`,\n ` const indexPath = join(clientDir, 'index.html')`,\n ` if (existsSync(indexPath)) return new Response(Bun.file(indexPath))`,\n ``,\n ` return notFoundResponse()`,\n `}`,\n ``,\n `console.log('Theo (Bun) listening on http://localhost:' + port)`,\n ]\n}\n\nexport async function buildBun(\n config: TheoConfig,\n cwd: string,\n deps: BunBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('bun', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const outputDir = resolve(cwd, '.theokit/bun')\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n ensureDir(outputDir)\n\n const pluginsPlan = planDeployedPlugins(config.plugins, 'bun')\n const entry = renderBunEntry(config.port, {\n ssrStreaming: config.ssrStreaming,\n securityHeaders: config.security?.headers,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n // #425 — the ONE concern that is not a literal. A closure cannot be baked, so a plugin\n // declared by module specifier is imported by the emitted module instead; a constructed one\n // is refused by name at build time rather than dropped in silence.\n runtimeConfigModule: pluginsPlan?.moduleSpecifier,\n })\n const write =\n deps.writeEntry ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n if (pluginsPlan !== undefined) {\n // Beside the entry, so the emitted import is a sibling. Written through the same seam as\n // the entry so a test that captures one captures both (#425).\n write(resolve(outputDir, 'theo.plugins.mjs'), pluginsPlan.source)\n }\n write(resolve(outputDir, 'server.mjs'), entry)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Bun output → .theokit/bun/server.mjs')\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'bun',\n securityHeaders: config.security?.headers,\n // No deploy target other than the streamed Cloudflare worker renders HTML\n // at request time, so none of the rest can mint a nonce.\n mintsNonce: false,\n // Bun serves `.theokit/client` itself, so the document DOES pass through\n // the handler these headers are attached to.\n documentHeaders: 'handler',\n })}\\n`,\n )\n}\n\nexport const bunAdapter: DeployAdapter = {\n name: 'bun',\n streamsResponses: true,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied: the entry carries `security.headers` as a\n // literal and puts the built baseline on every response, the served document\n // included.\n servesAgents: true,\n appliesConfig: [\n 'securityHeaders',\n 'csrf',\n 'disallowed',\n 'cors',\n 'serialization',\n 'plugins',\n // #508 — bun is the first Web target to ENFORCE a declared limit rather than refuse the\n // build: `Bun.serve` is long-lived, so the in-process counter survives, and its handler\n // already receives `server`, whose `requestIP` gives the peer address without a header.\n 'rateLimit',\n ],\n build(config, cwd, ctx) {\n return buildBun(config, cwd, {}, ctx)\n },\n}\n","/**\n * The rate limit a deployed entry carries, baked at build time.\n *\n * ## What #508 asked for, and what this answers\n *\n * `theokit build` refuses the six Web-standards targets when `theo.config.ts` declares a\n * `rateLimit` (`UnenforceableRateLimitError`). That refusal is honest and it is not enforcement.\n * The issue names three things standing between the refusal and a working limit: per-runtime\n * caller-address resolution, a refusal that survives where the address cannot be resolved, and\n * storage that outlives one invocation.\n *\n * For `bun` all three already have answers, which is why it is the target that moves first:\n *\n * - **Address** — `Bun.serve`'s handler is `fetch(request, server)`, and `server.requestIP(request)`\n * returns the peer address without depending on a proxy header a client could set.\n * - **Storage** — `Bun.serve` is a long-lived process. `createRateLimiterWeb`'s default\n * `InMemoryStore` therefore behaves exactly as it does under `theokit start`, which is the\n * deployment `node` already ships.\n * - **Refusal** — see below: every shape whose key cannot be resolved in a deployed entry throws\n * at BUILD time rather than degrading.\n *\n * ## Why the other five are not here\n *\n * Cloudflare, AWS Lambda, Netlify and Vercel are per-invocation runtimes: an in-process counter\n * does not survive between requests, so a limiter built on one forgets. A limit that forgets is a\n * limit that does not limit — the same class of failure as the shared bucket, reached by a\n * different road. Deno Deploy evicts isolates for the same reason. Those need an external counter,\n * which is a storage design and not a wiring change, so they keep refusing by name.\n *\n * Being explicit about that boundary is the point. The failure this whole area exists to prevent is\n * a config that reads as protection while protecting nothing.\n */\nimport type { TheoConfig } from '../config/schema.js'\n\ntype RateLimitConfig = NonNullable<TheoConfig['rateLimit']>\n\n/** The rate-limit slice of an adapter's build options, named once like `DeployedCorsOptions`. */\nexport interface DeployedRateLimitOptions {\n rateLimit?: RateLimitConfig\n}\n\n/**\n * Thrown at BUILD time when a declared rate limit cannot be carried into a deployed entry.\n *\n * Distinct from {@link UnenforceableRateLimitError}, and the distinction is not cosmetic: that one\n * means *this target enforces no limit at all*, this one means *this target enforces limits, and\n * cannot carry THIS one*. An operator reading the first looks for another target; an operator\n * reading the second changes the key.\n */\nexport class UnserialisableRateLimitError extends Error {\n override readonly name = 'UnserialisableRateLimitError'\n constructor(\n readonly target: string,\n readonly reason: string,\n ways: readonly string[],\n ) {\n super(\n [\n `Refusing to build for \\`${target}\\`: theo.config.ts declares a rate limit this target cannot carry.`,\n ``,\n ` ${reason}`,\n ``,\n ` Ways forward:`,\n ...ways.map((w) => ` • ${w}`),\n ``,\n ` This refuses rather than dropping the key, because a rate limit that silently does not`,\n ` apply looks exactly like one that does (usetheokit/theokit#461, #508).`,\n ].join('\\n'),\n )\n }\n}\n\n/** The base shape every accepted config narrows to: a window, a ceiling, and an IP key. */\ninterface BakeableRateLimit {\n windowMs: number\n max: number\n}\n\n/**\n * Narrow a declared rate limit to what a deployed entry can actually enforce, or refuse by name.\n *\n * Accepts the base `{ windowMs, max }` and the explicit `keyBy: 'ip'`, which is what the address\n * resolution below can key. Everything else throws:\n *\n * - a **function** `keyBy` has no literal — the same wall `deployed-cors.ts` hits on a callback\n * origin, and refused for the same reason;\n * - `keyBy: 'session' | 'user'` needs a session the deployed entry does not resolve at the point\n * the limit runs, which is before routing;\n * - `routes` needs the matched route, decided after this check. Refusing it is honest today and is\n * the natural next slice.\n */\nfunction bakeableRateLimit(\n rateLimit: RateLimitConfig | undefined,\n target: string,\n): BakeableRateLimit | undefined {\n if (rateLimit === undefined) return undefined\n const cfg = rateLimit as Record<string, unknown>\n\n if (typeof cfg.keyBy === 'function') {\n throw new UnserialisableRateLimitError(\n target,\n '`security.rateLimit.keyBy` is a function, and a deployed entry has no literal for a closure.',\n [\n \"use `keyBy: 'ip'` (the default), which this target resolves from the connection\",\n 'build for `node` and run `theokit start`, which can call the function',\n ],\n )\n }\n if (cfg.keyBy === 'session' || cfg.keyBy === 'user') {\n throw new UnserialisableRateLimitError(\n target,\n `\\`security.rateLimit.keyBy: '${cfg.keyBy}'\\` needs a resolved session, and the limit runs before routing.`,\n [\n \"use `keyBy: 'ip'`, which is resolvable at that point\",\n 'build for `node` and run `theokit start`',\n ],\n )\n }\n if (cfg.routes !== undefined) {\n throw new UnserialisableRateLimitError(\n target,\n '`security.rateLimit.routes` needs the matched route, which is decided after the limit runs here.',\n [\n 'declare a single global limit (`windowMs` + `max`)',\n 'build for `node` and run `theokit start`, which applies per-route limits',\n ],\n )\n }\n\n const windowMs = cfg.windowMs\n const max = cfg.max\n if (typeof windowMs !== 'number' || typeof max !== 'number') return undefined\n return { windowMs, max }\n}\n\n/**\n * The generated declarations and helper for a target that keys on the connection's peer address.\n *\n * `addressExpression` is the per-runtime half the issue calls out — the one thing that genuinely\n * differs between targets. Bun passes `server`; a future Deno slice would pass its\n * `ServeHandlerInfo`. Everything else here is shared.\n *\n * Returns `[]` when nothing was declared, so an app without a limit emits no limiter at all rather\n * than an inert one.\n */\nexport function deployedRateLimitFragment(\n rateLimit: RateLimitConfig | undefined,\n target: string,\n addressExpression: string,\n): string[] {\n const baked = bakeableRateLimit(rateLimit, target)\n if (baked === undefined) return []\n return [\n `// #508 — the limit the app declared, carried as a literal because a deployed entry has no`,\n `// theo.config.ts to read. The counter lives in this process, which outlives a request here.`,\n `const RATE_LIMIT = createRateLimiterWeb({ windowMs: ${baked.windowMs}, max: ${baked.max} })`,\n ``,\n `/**`,\n ` * The caller's address, from the connection rather than from a header.`,\n ` *`,\n ` * A header a client can set is a key a client can choose, which makes the bucket theirs to`,\n ` * split. An unresolved address falls back to one shared bucket — rare here, since that only`,\n ` * happens for a connection already gone, and the same fallback \\`theokit start\\` uses.`,\n ` */`,\n `function callerAddress(request, server) {`,\n ` return ${addressExpression} ?? 'unknown'`,\n `}`,\n ``,\n `/** The 429 a limited caller gets, carrying the limiter's own headers. */`,\n `function rateLimited(result) {`,\n ` return new Response(JSON.stringify({ error: { code: 'RATE_LIMITED', message: 'Too many requests' } }), {`,\n ` status: 429,`,\n ` headers: { 'Content-Type': 'application/json', ...result.headers },`,\n ` })`,\n `}`,\n ]\n}\n\n/**\n * The check itself, as generated source, placed inside the runtime's request handler.\n *\n * Extracted for the reason `bun.ts` extracts its other fragments: the emitter is one array literal,\n * so every line the entry gains counts against `max-lines-per-function`, and this one pushed it\n * past the ceiling.\n *\n * Runs AFTER the CORS preflight and BEFORE routing: a limited caller should not reach a handler,\n * and a browser still needs its CORS answer to read the 429 as a 429 rather than as a network\n * failure.\n */\nexport function rateLimitCheckFragment(\n rateLimit: RateLimitConfig | undefined,\n indent: string,\n): string[] {\n if (rateLimit === undefined) return []\n return [\n `${indent}const limit = RATE_LIMIT(callerAddress(request, server))`,\n `${indent}if (limit.limited) {`,\n `${indent} return withCors(request, withSecurityHeaders(rateLimited(limit), SECURITY_HEADERS))`,\n `${indent}}`,\n ]\n}\n"],"mappings":"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","names":[]}
@@ -150,6 +150,22 @@ async function dispatchWebhook(def, request) {
150
150
  });
151
151
  }
152
152
 
153
+ // src/server/webhook/providers/configured-secret.ts
154
+ function configuredSecrets(value, option) {
155
+ const secrets = typeof value === "string" ? [value] : value;
156
+ if (secrets.length === 0) return { ok: false, reason: `no ${option} configured` };
157
+ for (let i = 0; i < secrets.length; i++) {
158
+ if (secrets[i].length === 0) {
159
+ const where = typeof value === "string" ? option : `${option}[${i}]`;
160
+ return { ok: false, reason: `${where} is empty` };
161
+ }
162
+ }
163
+ return { ok: true, secrets };
164
+ }
165
+ function refusingVerifier(reason) {
166
+ return () => ({ ok: false, reason });
167
+ }
168
+
153
169
  // src/server/webhook/providers/stripe.ts
154
170
  function parseStripeHeader(header) {
155
171
  const parts = header.split(",").map((p) => p.trim());
@@ -199,7 +215,9 @@ var fromHex = (hex) => {
199
215
  };
200
216
  function stripe(opts) {
201
217
  const tolerance = opts.toleranceSeconds ?? 300;
202
- const secrets = Array.isArray(opts.secret) ? opts.secret : [opts.secret];
218
+ const configured = configuredSecrets(opts.secret, "secret");
219
+ if (!configured.ok) return refusingVerifier(configured.reason);
220
+ const { secrets } = configured;
203
221
  return async (req) => {
204
222
  const header = req.headers.get("stripe-signature");
205
223
  if (!header) {
@@ -233,28 +251,17 @@ function stripe(opts) {
233
251
  };
234
252
  }
235
253
 
236
- // src/server/webhook/providers/hub-signature-256.ts
237
- var HEADER = "x-hub-signature-256";
238
- var PREFIX = "sha256=";
239
- function fromHex2(hex) {
240
- if (hex.length % 2 !== 0) return null;
241
- const bytes = new Uint8Array(hex.length / 2);
242
- for (let i = 0; i < bytes.length; i++) {
243
- const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
244
- if (Number.isNaN(byte)) return null;
245
- bytes[i] = byte;
246
- }
247
- return bytes;
248
- }
249
- function verifyHubSignature256(secrets) {
254
+ // src/server/webhook/providers/hmac-sha256.ts
255
+ function verifyHmacSha256(opts) {
256
+ const { header, decode } = opts;
257
+ const configured = configuredSecrets(opts.secrets, opts.option);
258
+ if (!configured.ok) return refusingVerifier(configured.reason);
259
+ const { secrets } = configured;
250
260
  return async (req) => {
251
- const header = req.headers.get(HEADER);
252
- if (!header) return { ok: false, reason: `missing ${HEADER} header` };
253
- if (!header.startsWith(PREFIX)) {
254
- return { ok: false, reason: `malformed ${HEADER} header (missing ${PREFIX} prefix)` };
255
- }
256
- const signature = fromHex2(header.slice(PREFIX.length));
257
- if (!signature) return { ok: false, reason: "malformed signature hex" };
261
+ const value = req.headers.get(header);
262
+ if (!value) return { ok: false, reason: `missing ${header} header` };
263
+ const decoded = decode(value);
264
+ if (!decoded.ok) return { ok: false, reason: decoded.reason };
258
265
  const encoder = new TextEncoder();
259
266
  const body = encoder.encode(await req.text());
260
267
  let matched = false;
@@ -267,21 +274,42 @@ function verifyHubSignature256(secrets) {
267
274
  ["sign"]
268
275
  );
269
276
  const expected = new Uint8Array(await globalThis.crypto.subtle.sign("HMAC", key, body));
270
- if (signature.length !== expected.length) continue;
271
- if (timingSafeEqual(signature, expected)) matched = true;
277
+ if (decoded.bytes.length !== expected.length) continue;
278
+ if (timingSafeEqual(decoded.bytes, expected)) matched = true;
272
279
  }
273
280
  return matched ? { ok: true } : { ok: false, reason: "signature mismatch" };
274
281
  };
275
282
  }
276
283
 
284
+ // src/server/webhook/providers/hub-signature-256.ts
285
+ var HEADER = "x-hub-signature-256";
286
+ var PREFIX = "sha256=";
287
+ function fromPrefixedHex(value) {
288
+ if (!value.startsWith(PREFIX)) {
289
+ return { ok: false, reason: `malformed ${HEADER} header (missing ${PREFIX} prefix)` };
290
+ }
291
+ const hex = value.slice(PREFIX.length);
292
+ if (hex.length % 2 !== 0) return { ok: false, reason: "malformed signature hex" };
293
+ const bytes = new Uint8Array(hex.length / 2);
294
+ for (let i = 0; i < bytes.length; i++) {
295
+ const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
296
+ if (Number.isNaN(byte)) return { ok: false, reason: "malformed signature hex" };
297
+ bytes[i] = byte;
298
+ }
299
+ return { ok: true, bytes };
300
+ }
301
+ function verifyHubSignature256(secrets, option) {
302
+ return verifyHmacSha256({ header: HEADER, secrets, option, decode: fromPrefixedHex });
303
+ }
304
+
277
305
  // src/server/webhook/providers/github.ts
278
306
  function github(opts) {
279
- return verifyHubSignature256(Array.isArray(opts.secret) ? opts.secret : [opts.secret]);
307
+ return verifyHubSignature256(opts.secret, "secret");
280
308
  }
281
309
 
282
310
  // src/server/webhook/providers/slack.ts
283
311
  var PREFIX2 = "v0=";
284
- function fromHex3(hex) {
312
+ function fromHex2(hex) {
285
313
  if (hex.length % 2 !== 0) return null;
286
314
  const bytes = new Uint8Array(hex.length / 2);
287
315
  for (let i = 0; i < bytes.length; i++) {
@@ -293,7 +321,9 @@ function fromHex3(hex) {
293
321
  }
294
322
  function slack(opts) {
295
323
  const tolerance = opts.toleranceSeconds ?? 300;
296
- const signingSecret = opts.signingSecret;
324
+ const configured = configuredSecrets(opts.signingSecret, "signingSecret");
325
+ if (!configured.ok) return refusingVerifier(configured.reason);
326
+ const signingSecret = configured.secrets[0];
297
327
  return async (req) => {
298
328
  const tsHeader = req.headers.get("x-slack-request-timestamp");
299
329
  const sigHeader = req.headers.get("x-slack-signature");
@@ -331,7 +361,7 @@ function slack(opts) {
331
361
  const sigBuf = await globalThis.crypto.subtle.sign("HMAC", key, enc2.encode(base));
332
362
  const expectedBytes = new Uint8Array(sigBuf);
333
363
  const sigHex = sigHeader.slice(PREFIX2.length);
334
- const sigBytes = fromHex3(sigHex);
364
+ const sigBytes = fromHex2(sigHex);
335
365
  if (!sigBytes) {
336
366
  return { ok: false, reason: "malformed signature hex" };
337
367
  }
@@ -347,8 +377,10 @@ function slack(opts) {
347
377
 
348
378
  // src/server/webhook/providers/telegram.ts
349
379
  function telegram(opts) {
380
+ const configured = configuredSecrets(opts.secretToken, "secretToken");
381
+ if (!configured.ok) return refusingVerifier(configured.reason);
350
382
  const enc2 = new TextEncoder();
351
- const expected = enc2.encode(opts.secretToken);
383
+ const expected = enc2.encode(configured.secrets[0]);
352
384
  return (req) => {
353
385
  const header = req.headers.get("x-telegram-bot-api-secret-token");
354
386
  if (!header) {
@@ -361,7 +393,7 @@ function telegram(opts) {
361
393
  }
362
394
 
363
395
  // src/server/webhook/providers/discord.ts
364
- function fromHex4(hex) {
396
+ function fromHex3(hex) {
365
397
  if (hex.length === 0 || hex.length % 2 !== 0) return null;
366
398
  const bytes = new Uint8Array(hex.length / 2);
367
399
  for (let i = 0; i < bytes.length; i++) {
@@ -372,14 +404,16 @@ function fromHex4(hex) {
372
404
  return bytes;
373
405
  }
374
406
  function discord(opts) {
375
- const keyBytes = fromHex4(opts.publicKey);
407
+ const configured = configuredSecrets(opts.publicKey, "publicKey");
408
+ if (!configured.ok) return refusingVerifier(configured.reason);
409
+ const keyBytes = fromHex3(configured.secrets[0]);
376
410
  return async (req) => {
377
411
  if (!keyBytes) return { ok: false, reason: "malformed public key hex" };
378
412
  const sigHex = req.headers.get("x-signature-ed25519");
379
413
  const timestamp = req.headers.get("x-signature-timestamp");
380
414
  if (!sigHex) return { ok: false, reason: "missing x-signature-ed25519 header" };
381
415
  if (!timestamp) return { ok: false, reason: "missing x-signature-timestamp header" };
382
- const sigBytes = fromHex4(sigHex);
416
+ const sigBytes = fromHex3(sigHex);
383
417
  if (!sigBytes) return { ok: false, reason: "malformed signature hex" };
384
418
  const rawBody = await req.text();
385
419
  const message = new Uint8Array(new TextEncoder().encode(timestamp + rawBody));
@@ -404,13 +438,16 @@ function discord(opts) {
404
438
 
405
439
  // src/server/webhook/providers/whatsapp.ts
406
440
  function whatsapp(opts) {
407
- return verifyHubSignature256(
408
- Array.isArray(opts.appSecret) ? opts.appSecret : [opts.appSecret]
409
- );
441
+ return verifyHubSignature256(opts.appSecret, "appSecret");
410
442
  }
411
443
  function whatsappSubscribe(opts) {
444
+ const configured = configuredSecrets(opts.verifyToken, "verifyToken");
445
+ if (!configured.ok) {
446
+ const { reason } = configured;
447
+ return () => ({ ok: false, reason });
448
+ }
412
449
  const encoder = new TextEncoder();
413
- const expected = encoder.encode(opts.verifyToken);
450
+ const expected = encoder.encode(configured.secrets[0]);
414
451
  return (req) => {
415
452
  const params = new URL(req.url).searchParams;
416
453
  if (params.get("hub.mode") !== "subscribe") {
@@ -430,6 +467,31 @@ function whatsappSubscribe(opts) {
430
467
  };
431
468
  }
432
469
 
470
+ // src/server/webhook/providers/line.ts
471
+ var HEADER2 = "x-line-signature";
472
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
473
+ function fromBase64(value) {
474
+ if (value.length === 0 || value.length % 4 !== 0 || !BASE64.test(value)) {
475
+ return { ok: false, reason: "malformed signature base64" };
476
+ }
477
+ try {
478
+ const binary = atob(value);
479
+ const bytes = new Uint8Array(binary.length);
480
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
481
+ return { ok: true, bytes };
482
+ } catch {
483
+ return { ok: false, reason: "malformed signature base64" };
484
+ }
485
+ }
486
+ function line(options) {
487
+ return verifyHmacSha256({
488
+ header: HEADER2,
489
+ secrets: options.channelSecret,
490
+ option: "channelSecret",
491
+ decode: fromBase64
492
+ });
493
+ }
494
+
433
495
  export {
434
496
  timingSafeEqual,
435
497
  DEFAULT_MAX_BODY_BYTES,
@@ -443,6 +505,7 @@ export {
443
505
  telegram,
444
506
  discord,
445
507
  whatsapp,
446
- whatsappSubscribe
508
+ whatsappSubscribe,
509
+ line
447
510
  };
448
- //# sourceMappingURL=chunk-KPZ2JSAU.js.map
511
+ //# sourceMappingURL=chunk-EW5IG7GA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/webhook/timing-safe-equal.ts","../src/server/webhook/raw-body.ts","../src/server/webhook/define-webhook.ts","../src/server/webhook/providers/configured-secret.ts","../src/server/webhook/providers/stripe.ts","../src/server/webhook/providers/hmac-sha256.ts","../src/server/webhook/providers/hub-signature-256.ts","../src/server/webhook/providers/github.ts","../src/server/webhook/providers/slack.ts","../src/server/webhook/providers/telegram.ts","../src/server/webhook/providers/discord.ts","../src/server/webhook/providers/whatsapp.ts","../src/server/webhook/providers/line.ts"],"mappings":"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","names":["result","PREFIX","fromHex","enc","enc","fromHex","HEADER"]}
@@ -82,6 +82,9 @@ function deployedCsrfFragment(opts, home = "a deployed function") {
82
82
  }
83
83
 
84
84
  // src/adapters/deployed-runtime-config.ts
85
+ function serverDirLiteral(opts) {
86
+ return JSON.stringify(opts.serverDir ?? "server");
87
+ }
85
88
  var EMPTY = {
86
89
  imports: [],
87
90
  declarations: [],
@@ -180,9 +183,10 @@ function describeDeployedSecurityHeaders(limits) {
180
183
  export {
181
184
  deployedCorsFragment,
182
185
  deployedCsrfFragment,
186
+ serverDirLiteral,
183
187
  deployedRuntimeConfigFragment,
184
188
  deployedTraceFragment,
185
189
  renderSecurityHeadersConfigLiteral,
186
190
  describeDeployedSecurityHeaders
187
191
  };
188
- //# sourceMappingURL=chunk-VPX3MS5D.js.map
192
+ //# sourceMappingURL=chunk-PALZUV6E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/deployed-cors.ts","../src/adapters/deployed-csrf.ts","../src/adapters/deployed-runtime-config.ts","../src/adapters/deployed-trace.ts","../src/adapters/security-headers.ts"],"sourcesContent":["/**\n * The CORS configuration a deployed entry carries, baked at build time.\n *\n * ## The defect this closes\n *\n * `security.cors` reached exactly one consumer: Vite's `configureServer` hook. So an app that\n * worked cross-origin under `theokit dev` stopped working the moment anything else served it —\n * `theokit start` (fixed separately) and all six Web deploy targets (usetheokit/theokit#409). Same\n * config, same code, no error and no warning; the failure surfaces in a browser as a blocked fetch\n * on the deployed URL, three layers from the key that had quietly stopped being read.\n *\n * The pure half was written twice and called once: `createCorsWebHandler` — the Web mirror — had no\n * caller anywhere in the repository. Nothing here reimplements it.\n *\n * ## A callback origin is REFUSED, not silently dropped\n *\n * `corsSchema.origins` accepts `z.function(...)` alongside the string / RegExp / array shapes. A\n * deployed function has no `theo.config.ts` to read, and there is no literal for a closure — so a\n * build that baked only the serialisable shapes would produce an app whose CORS silently allowed\n * nothing, which is the exact class of failure this issue reports.\n *\n * `rules/three-target-parity.md` § 3 is explicit about the alternative: \"a target that cannot serve\n * a capability refuses by name. Silent degradation is the failure mode this rule exists to\n * prevent.\" So the build throws, naming the target, the key and the two ways forward.\n */\nimport type { TheoConfig } from '../config/schema.js'\n\ntype CorsConfig = NonNullable<NonNullable<TheoConfig['security']>['cors']>\n\n/**\n * The CORS slice of an adapter's build options.\n *\n * Narrow and named once, matching how `securityHeaders` and `DeployedCsrfOptions` already reach the\n * emitters: each renderer receives what it uses, and six signatures name one type instead of six\n * inline shapes that can drift.\n */\nexport interface DeployedCorsOptions {\n cors?: CorsConfig\n}\n\n/**\n * Thrown at BUILD time when the declared CORS cannot be carried to a deployed target.\n *\n * A build error is the point: the alternative is a deploy that looks configured and refuses every\n * cross-origin request, discovered by a browser rather than by the build.\n */\nexport class UnserializableCorsOriginError extends Error {\n constructor(target: string) {\n super(\n `security.cors.origins is a function, and the \\`${target}\\` target cannot carry it: a deployed ` +\n `function has no theo.config.ts to read, and a callback cannot be written into the emitted ` +\n `entry. Replace it with the origin, a RegExp, or an array of either — all of which travel — ` +\n `or build for \\`node\\` and run \\`theokit start\\`, which evaluates the callback at runtime.`,\n )\n this.name = 'UnserializableCorsOriginError'\n }\n}\n\n/** Source text for one origin matcher: a RegExp as a literal, anything else as JSON. */\nfunction renderOrigin(origin: unknown, target: string): string {\n if (typeof origin === 'function') throw new UnserializableCorsOriginError(target)\n if (origin instanceof RegExp) return String(origin)\n if (Array.isArray(origin)) return `[${origin.map((o) => renderOrigin(o, target)).join(', ')}]`\n return JSON.stringify(origin)\n}\n\n/**\n * Source text for the CORS config, or `undefined` when the app declared none.\n *\n * RegExp entries are emitted as regex literals for the reason `deployed-csrf.ts` gives at length:\n * `JSON.stringify` renders a RegExp as `{}`, and `matchesOrigin` checks `instanceof RegExp`, so a\n * JSON-rendered origin would sit in the emitted file looking configured and matching nothing.\n *\n * @throws UnserializableCorsOriginError when `origins` is a callback\n */\nexport function renderDeployedCorsLiteral(cors: CorsConfig | undefined, target: string): string {\n if (cors === undefined) return 'undefined'\n\n const parts = [`origins: ${renderOrigin(cors.origins, target)}`]\n if (cors.methods !== undefined) parts.push(`methods: ${JSON.stringify(cors.methods)}`)\n if (cors.allowedHeaders !== undefined)\n parts.push(`allowedHeaders: ${JSON.stringify(cors.allowedHeaders)}`)\n if (cors.exposedHeaders !== undefined)\n parts.push(`exposedHeaders: ${JSON.stringify(cors.exposedHeaders)}`)\n // Both carry schema defaults, so they are always present and always emitted — unlike the\n // optional fields above, whose absence is a real answer the handler already has one for.\n parts.push(\n `credentials: ${JSON.stringify(cors.credentials)}`,\n `maxAge: ${JSON.stringify(cors.maxAge)}`,\n )\n\n return `{ ${parts.join(', ')} }`\n}\n\n/**\n * The lines that declare the CORS handler in a generated entry.\n *\n * `null` when nothing was declared, which is what \"no cors block\" meant before and still means: no\n * headers, not permissive ones.\n */\nexport function deployedCorsFragment(cors: CorsConfig | undefined, target: string): string[] {\n return [\n `// #409 — the CORS the app declared, carried as a literal because a deployed function has no`,\n `// theo.config.ts to read. \\`null\\` when the app declared none: no headers, not permissive ones.`,\n `const CORS_CONFIG = ${renderDeployedCorsLiteral(cors, target)}`,\n `const CORS_HANDLER = CORS_CONFIG === undefined ? null : createCorsWebHandler(CORS_CONFIG)`,\n ``,\n `/** Answer a preflight before routing — an OPTIONS the router handles never gets a CORS answer. */`,\n `function corsPreflight(request) {`,\n ` return CORS_HANDLER === null ? null : CORS_HANDLER.handlePreflightRequest(request)`,\n `}`,\n ``,\n `/** Put the headers on whatever the app answered, including its 404s — a browser reads a 404`,\n ` * without them as a CORS failure rather than as the 404 it is. */`,\n `function withCors(request, response) {`,\n ` if (CORS_HANDLER !== null) CORS_HANDLER.applyCorsHeaders(request, response.headers)`,\n ` return response`,\n `}`,\n ]\n}\n","/**\n * The CSRF configuration a deployed entry carries, baked at build time.\n *\n * ## The defect this closes\n *\n * The six Web-standards adapter entries built `executeRoute`'s context from an eight-field\n * literal, and neither `csrfMode` nor `disallowed` was among the eight (usetheokit/theokit#410).\n * `executeRoute` defaults an absent mode to `'strict'`, so an app declaring\n * `security: { csrf: 'off' }` — or `'warn'` — got `'strict'` on every deploy target: a `POST` that\n * works under `theokit dev` and `theokit start` answers `403 CSRF_INVALID` on Vercel, naming a\n * mechanism the operator had switched off. The config still validated and the build still\n * succeeded; the behaviour simply changed.\n *\n * The deployed function has no `theo.config.ts` to read, which is why the value is carried as a\n * literal — the same shape `security.headers` already uses (`renderSecurityHeadersConfigLiteral`).\n *\n * ## Why this is not `JSON.stringify`\n *\n * `disallowed.routes` accepts RegExp entries (`config/schemas/security.ts`), and `JSON.stringify`\n * renders a RegExp as `{}`. That is not a formatting problem: `matchDisallowed` checks\n * `p instanceof RegExp`, so a `{}` matches nothing while reading, in the emitted file, as a rule\n * that is present and configured. Reaching for JSON here would reproduce this issue's own defect —\n * configuration that survives validation and quietly stops applying — one layer further down.\n */\nimport type { TheoConfig } from '../config/schema.js'\n\ntype SecurityConfig = NonNullable<TheoConfig['security']>\n\n/**\n * The two slices of `security` that reach `executeRoute`'s context.\n *\n * Narrow rather than the whole block, matching how `securityHeaders` is already passed: each\n * renderer receives what it uses and nothing else, so a headers change cannot reach the CSRF\n * literal and vice versa.\n */\nexport interface DeployedCsrfOptions {\n csrf?: SecurityConfig['csrf']\n disallowed?: SecurityConfig['disallowed']\n}\n\n/**\n * Source text for one route pattern.\n *\n * A RegExp is emitted as a regex literal so it arrives as a RegExp; a string goes through\n * `JSON.stringify`, which is the correct escaper for a JS string literal (quotes, backslashes,\n * control characters, line separators).\n */\nfunction renderRoutePattern(pattern: string | RegExp): string {\n return pattern instanceof RegExp ? String(pattern) : JSON.stringify(pattern)\n}\n\n/**\n * Source text for the CSRF slice of `executeRoute`'s context.\n *\n * Absent values are OMITTED rather than defaulted. `executeRoute` already defaults an absent\n * `csrfMode` to `'strict'`, and writing `'strict'` here would put that default in a second place\n * where the two can disagree — which is the class of drift the whole issue is about.\n *\n * @param security - the declared csrf slices, or `undefined` when the app declared no security block\n */\nexport function renderDeployedCsrfLiteral(security: DeployedCsrfOptions | undefined): string {\n if (security === undefined) return '{}'\n\n const parts: string[] = []\n if (security.csrf !== undefined) parts.push(`csrfMode: ${JSON.stringify(security.csrf)}`)\n\n const { disallowed } = security\n if (disallowed !== undefined) {\n const routes = disallowed.routes.map(renderRoutePattern).join(', ')\n parts.push(\n `disallowed: { routes: [${routes}], behavior: ${JSON.stringify(disallowed.behavior)} }`,\n )\n }\n\n // `{}` and not `{ }` when nothing was declared: the emitted file is read by people, and the\n // two-space version reads as though something was meant to be there.\n return parts.length === 0 ? '{}' : `{ ${parts.join(', ')} }`\n}\n\n/**\n * The lines that declare `CSRF_CONFIG` in a generated entry.\n *\n * One function rather than the same six lines pasted into each of the six adapters: that\n * duplication is how the eight-field context literal came to be wrong in six places at once, and\n * repeating the fix in the same shape would leave the next field with the same six places to be\n * forgotten in. It also keeps the two largest emitters under the `max-lines-per-function` ceiling,\n * which `vercel.ts` already extracts fragments to respect.\n *\n * @param opts - the declared csrf slices, passed straight through from the adapter's build options\n * @param home - what the target has instead of a config file, for the comment's second sentence\n */\nexport function deployedCsrfFragment(\n opts: DeployedCsrfOptions,\n home = 'a deployed function',\n): string[] {\n return [\n `// #410 — the CSRF mode and per-route escalation the app declared. Carried as a`,\n `// literal for the same reason as the headers above: ${home} has no theo.config.ts`,\n `// to read. Absent keys stay absent so executeRoute's own default ('strict') applies,`,\n `// rather than this file becoming a second place it can drift.`,\n `const CSRF_CONFIG = ${renderDeployedCsrfLiteral(opts)}`,\n ]\n}\n","/**\n * The configuration a deployed entry could not apply (usetheokit/theokit#425).\n *\n * ## Why this is not another literal renderer\n *\n * `deployed-csrf.ts` and `deployed-cors.ts` bake their values into the emitted source, which works\n * because `csrf` is an enum and `disallowed` is a `{ routes, behavior }` object — plain data, and a\n * deployed function has no `theo.config.ts` to read.\n *\n * The two concerns left over from #410 turn out to be different from each other, and the difference\n * is the whole design:\n *\n * - **`serialization` is plain data too.** The config field is `z.enum(['json', 'superjson'])`\n * (`config/schema.ts:147`) — a selector, not a transformer. `resolveTransformer` turns it into the\n * functions, and it already ships from `theokit/server`. So this half is a literal like the rest,\n * and the deployed entry resolves it exactly the way `theokit start` does\n * (`cli/commands/start/index.ts:108`), from the same string, through the same function.\n * - **`plugins` genuinely carries functions.** A plugin is constructed in `theo.config.ts` and there\n * is no literal for a closure, so this half needs the entry to import a module instead.\n *\n * ## Static import, not `import()` in the request path\n *\n * The tempting shortcut is `await import('../../theo.config.js')` inside the handler. It trades a\n * silent failure for a louder one on targets with no filesystem, and it moves configuration\n * resolution into every request on the targets that do have one.\n *\n * What this emits instead is a TOP-LEVEL import of a module the build already resolved and wrote\n * beside the entry — the same shape `renderBakedRoutes` uses for route modules (#369): decide on\n * the build machine, emit a static specifier. It is evaluated once at module load, the target's\n * bundler can see through it, and a plugin that needs an API the target lacks fails the build\n * rather than the first request.\n *\n * ## Why each half is optional\n *\n * An app that declares neither concern must produce the entry it produced before this existed.\n * Importing a module the build did not emit fails at load, and spreading an empty object costs an\n * allocation per request for nothing. So an empty request renders to nothing at all, and the\n * caller's `executeRoute` literal is unchanged.\n *\n * The halves are independent on purpose: an app that only picks `superjson` must not be made to\n * carry a plugins module, and an app with plugins and default JSON must not gain a transformer\n * lookup. Coupling them would have made the common case pay for the rare one.\n */\n\n/**\n * The option every Web-standards adapter grows to carry non-serialisable configuration.\n *\n * Composed into each adapter's option type the way `DeployedCsrfOptions` already is, so the six\n * targets cannot drift into six spellings of the same field.\n */\n/**\n * The project's `serverDir`, carried into every generated entrypoint (#RFC server-layout).\n *\n * Separate from the other option groups because it is not a runtime feature toggle: it is the\n * project's own layout, and an adapter that hardcodes `'server'` agrees with the default by\n * COINCIDENCE. The moment a project sets the option — the entire point of it existing — the\n * generated entrypoint resolves a directory that is not there, and only after deploy: the build\n * succeeds, the bundle is written, and routes 404 in production with nothing naming the cause.\n */\nexport interface DeployedServerDirOptions {\n /** Project-relative server directory. Absent ⇒ the schema default, `server`. */\n serverDir?: string\n}\n\n/**\n * The server directory as a quoted TypeScript literal, ready to interpolate into generated source.\n *\n * `JSON.stringify` rather than wrapping in quotes by hand: the value reaches this from user config,\n * so a directory containing a quote or a backslash would otherwise emit a syntax error into\n * somebody else's build — a worse failure than the one this fixes.\n */\nexport function serverDirLiteral(opts: DeployedServerDirOptions): string {\n return JSON.stringify(opts.serverDir ?? 'server')\n}\n\nexport interface DeployedRuntimeConfigOptions {\n /**\n * Specifier of the plugins module the build wrote beside the entry, or `undefined` when the app\n * declares no plugins and the build wrote none.\n */\n runtimeConfigModule?: string\n /**\n * The app's `serialization` selector, carried as a literal.\n *\n * `'json'` and `undefined` both mean the default, and neither emits anything: `executeRoute`\n * already falls back to `JSON.stringify`, and the `x-theo-transformer` header is deliberately\n * absent for the default so a client is told only when there is something to be told.\n */\n serialization?: 'json' | 'superjson'\n}\n\n/** The three places an entry has to grow to carry non-serialisable configuration. */\nexport interface DeployedRuntimeConfigFragment {\n /** Top-level imports. Empty when the build emitted no config module. */\n readonly imports: string[]\n /** Module-scope declarations — evaluated once, at load. Empty when there is nothing to carry. */\n readonly declarations: string[]\n /**\n * Spread into the entry's `executeRoute({ … })` literal, inside an async function.\n *\n * Empty string when there is nothing to carry, so the call site keeps the exact shape it had.\n */\n readonly executeRouteSpread: string\n}\n\nconst EMPTY: DeployedRuntimeConfigFragment = {\n imports: [],\n declarations: [],\n executeRouteSpread: '',\n}\n\n/**\n * What a deployed entry needs in order to apply `config.plugins` and `config.serialization`.\n *\n * @param moduleSpecifier - specifier of the runtime-config module the build emitted beside the\n * entry, or `undefined` when the app declared neither concern and the build emitted none.\n */\nexport function deployedRuntimeConfigFragment(\n options: DeployedRuntimeConfigOptions | undefined,\n): DeployedRuntimeConfigFragment {\n const pluginsModule = options?.runtimeConfigModule\n // 'json' is the default and emits nothing — see `serialization` above.\n const serialization = options?.serialization === 'superjson' ? 'superjson' : undefined\n if (pluginsModule === undefined && serialization === undefined) return EMPTY\n\n const imports: string[] = []\n const declarations: string[] = []\n const spread: string[] = []\n\n if (pluginsModule !== undefined) {\n imports.push(\n `import { createPluginRunnerFromConfig } from 'theokit/server'`,\n `// #425 — the app's own plugins, resolved on the build machine and written beside this entry.`,\n `// A closure has no literal, so this is an import rather than a baked value.`,\n `import theoRuntimeConfig from '${pluginsModule}'`,\n )\n declarations.push(\n `// Built ONCE, at module load. A runner rebuilt per request would re-run every plugin's`,\n `// \\`register\\`, which is where a plugin allocates the state its hooks then read.`,\n `const THEO_PLUGIN_RUNNER = createPluginRunnerFromConfig(theoRuntimeConfig.plugins)`,\n )\n // Awaited, not passed along: `createPluginRunnerFromConfig` is async because `register` is, and\n // a pending promise handed to `executeRoute` is a truthy object with none of the runner's\n // methods — every hook would silently not fire, which is this issue's own defect one layer in.\n spread.push(`pluginRunner: await THEO_PLUGIN_RUNNER`)\n }\n\n if (serialization !== undefined) {\n imports.push(`import { resolveTransformer } from 'theokit/server'`)\n declarations.push(\n `// #425 — a literal, because \\`config.serialization\\` is a SELECTOR and not a transformer.`,\n `// Same string, same function \\`theokit start\\` calls, so the deployed response and the local`,\n `// one cannot disagree about what the app asked for — including the \\`x-theo-transformer\\``,\n `// header, whose absence is what made this a data bug rather than a formatting one.`,\n `const THEO_TRANSFORMER = resolveTransformer('${serialization}')`,\n )\n spread.push(`transformer: THEO_TRANSFORMER`)\n }\n\n return { imports, declarations, executeRouteSpread: `${spread.join(', ')},` }\n}\n","/**\n * The request id a deployed entry uses, and the header it echoes back.\n *\n * ## The defect this closes\n *\n * Every generated entry minted a fresh `randomUUID()` per request and set no correlation header\n * at all on a success path (usetheokit/theokit#410). Both Node paths do the opposite: they resolve\n * an incoming `traceparent` / `x-request-id` through `extractTraceId` and echo the result under\n * both `x-request-id` and `x-trace-id` (`cli/commands/start/request-handler.ts`,\n * `vite-plugin/api-middleware.ts`).\n *\n * The consequence is that a trace crossing into a deployed function starts over. The caller's id\n * is discarded, and the response carries nothing to correlate against — so a request that fails in\n * production cannot be tied to the client that made it, which is the one situation the id exists\n * for.\n *\n * ## Why `setHeader` before the handler, rather than wrapping the response\n *\n * It is what the Node path does, and the shim reproduces Node's semantics exactly: `writeHead`\n * MERGES into the header map rather than replacing it (`web-shim.ts`), so a header set here\n * survives the handler's own `writeHead` and a handler that sets its own id still wins. Wrapping\n * the finished `Response` instead would have to mutate a response whose body is already streaming\n * (#382), and would miss the branches that return before the shim is built.\n */\n\n/**\n * Lines that resolve the request id and echo it, as generated source.\n *\n * @param requestVar - the name the entry gave the Web `Request` in scope\n * @param indent - leading whitespace, so the emitted file stays readable\n */\nexport function deployedTraceFragment(requestVar: string, indent: string): string[] {\n return [\n `${indent}// #410 — honour the caller's trace id instead of minting a new one, and echo it.`,\n `${indent}// \\`extractTraceIdFromRequest\\` validates the caller-controlled \\`x-request-id\\``,\n `${indent}// before trusting it, and falls back to a fresh UUID when neither header is present.`,\n `${indent}const requestId = extractTraceIdFromRequest(${requestVar})`,\n `${indent}res.setHeader('x-request-id', requestId)`,\n `${indent}res.setHeader(TRACE_HEADER, requestId)`,\n ]\n}\n","/**\n * The security headers a deployed target puts on its responses.\n *\n * `theokit start` applies the configured baseline to every response it writes\n * (`cli/commands/start/request-handler.ts`). None of the six Web-standards\n * deploy adapters applied any, so the same page carried a CSP,\n * `X-Frame-Options`, HSTS and `nosniff` under `theokit start` and none of them\n * once deployed (usetheokit/theokit#410, GHSA-87qq-fgcr-384x).\n *\n * This module is the seam that closes that half of the gap. It has two halves\n * and they live together on purpose: the code that writes the literal into a\n * generated entry and the code that reads it at request time have to agree on\n * one shape, and a shape stated in two files drifts.\n *\n * - **Build time** — {@link renderSecurityHeadersConfigLiteral} turns\n * `security.headers` into a JSON literal the adapter inlines. A deployed\n * runtime has no `theo.config.ts` to read, so the configuration travels as\n * data.\n * - **Request time** — the generated entry calls {@link buildSecurityHeaders}\n * on that literal and hands every response to {@link withSecurityHeaders}.\n * The same function `theokit start` calls, on the same input, so the two\n * cannot disagree about what the configuration means.\n *\n * ## The per-request nonce, and where it stops\n *\n * `buildSecurityHeaders` accepts a per-request `nonce` and substitutes it into\n * `script-src`. A nonce cannot survive a build-time literal — it is minted per\n * response — so a target reaches one only if it renders the HTML at request\n * time and can put the same value on the script tags it emits.\n *\n * Exactly one deploy path does: **Cloudflare with `ssrStreaming: true`**, whose\n * worker calls `renderStreamingWeb(request, { nonce })`, and that renderer\n * threads the value into `renderToReadableStream` and into the hydration script\n * (`router/entry-server.ts`). That branch mints a nonce per request and builds\n * its CSP from it. Everything else serves HTML written at build time, or no\n * HTML at all, and carries a **nonce-less CSP** — the same answer\n * `buildSecurityHeaders` already gives a prerendered route (EC-4), for the same\n * reason: a nonce in the header with no nonce on the tag blocks every inline\n * script.\n *\n * That asymmetry is real and is not smoothed over. It is stated in the emitted\n * entry, printed by the build through\n * {@link describeDeployedSecurityHeaders}, and written down in\n * `docs/surfaces/build-adapters.md`.\n */\nimport { generateNonce } from '../core/contracts/nonce.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { buildSecurityHeaders } from '../core/contracts/security-headers.js'\n\n/**\n * Re-exported so a generated entry has ONE import for the whole concern.\n *\n * Reaching it through `theokit/server/security` would work and would also drag\n * that barrel's CSRF surface into a Worker bundle, for a function that is forty\n * lines of string concatenation. It is defined in `core/contracts/`, which is\n * the module every target may import from — `adapters → server` is not an edge\n * in the DAG, and the header policy was never server code.\n */\nexport { buildSecurityHeaders }\n\n/**\n * Re-exported for the same reason, and so the streamed Cloudflare worker mints\n * its nonce with the identical primitive `theokit start` uses\n * (`cli/commands/start/request-handler.ts`) rather than a second, hand-rolled\n * one. It is already runtime-portable: Web Crypto first, with a named error\n * when the runtime has none.\n */\nexport { generateNonce }\n\n/**\n * The `security.headers` block, as a literal a generated entry can carry.\n *\n * `{}` when the app declares none — which is not the same as \"no headers\".\n * `buildSecurityHeaders({})` returns the full default baseline, and `{}` is\n * exactly what `theokit start` passes when `security.headers` is absent\n * (`cli/commands/start/index.ts`). An app with no security block gets the same\n * baseline deployed as it gets locally.\n */\nexport function renderSecurityHeadersConfigLiteral(\n headers: SecurityHeadersConfig | undefined,\n): string {\n return JSON.stringify(headers ?? {})\n}\n\n/**\n * Put the headers on a response, without overruling the handler.\n *\n * `theokit start` sets the baseline BEFORE the route handler runs, so a handler\n * can override it with `res.setHeader` (last write wins, Node convention). On a\n * Web target the response arrives already built, so the equivalent of \"the\n * handler wins\" is to skip a header the response already carries: a route that\n * set its own `Content-Security-Policy` keeps it.\n *\n * Mutates and returns the same `Response` rather than constructing a\n * replacement, because these responses are handed to the runtime while their\n * body is still being written (#382) and re-wrapping the stream is exactly the\n * second buffering point that change removed.\n *\n * Mutation is safe for every response these entries produce: the Fetch spec\n * gives a locally constructed `Response` the `response` header guard, which\n * permits `set` for every name used here. The `immutable` guard belongs to\n * responses that came back from `fetch()`, and none of the six emitted handlers\n * returns one — each builds its response from `createWebShim`, from a `new\n * Response(...)`, or from the SSR renderer. The WebSocket upgrade, which is the\n * one response an adapter gets from its runtime rather than building, is\n * deliberately not routed through here.\n */\nexport function withSecurityHeaders(response: Response, headers: Record<string, string>): Response {\n for (const [key, value] of Object.entries(headers)) {\n if (!response.headers.has(key)) response.headers.set(key, value)\n }\n return response\n}\n\nexport interface DeployedSecurityHeaderLimits {\n target: string\n /** The app's `security.headers` block, or undefined when it declares none. */\n securityHeaders: SecurityHeadersConfig | undefined\n /**\n * Does the emitted handler render HTML at request time and mint a CSP nonce\n * for it? True only for Cloudflare with `ssrStreaming: true`.\n */\n mintsNonce: boolean\n /**\n * Is the HTML document served by a platform static host rather than by the\n * handler this build emits? True wherever the emitted handler answers\n * `/api/*` and returns 404 for everything else.\n */\n /**\n * Who puts the security headers on the HTML DOCUMENT — a different question from who serves it,\n * and the two used to be collapsed into one boolean (usetheokit/theokit#412).\n *\n * - `handler` — this target's own handler returns the document, so it carries the same baseline\n * every API response carries. No caveat.\n * - `platform-configured` — the platform's static host serves the document, AND this build emits\n * the configuration that puts the headers on it (`.vercel/output/config.json`, `netlify.toml`).\n * Still worth stating, because nothing here has seen a deployed response.\n * - `platform-unmanaged` — the platform serves it and this build owns no artifact that could\n * configure it. This is the real remaining gap, and it stays named.\n *\n * The two-value version reported `platform-configured` targets with the same message as\n * `platform-unmanaged` ones, telling an operator to go and do work the build had already done —\n * and a stale limitation reads exactly like a current one.\n */\n documentHeaders: 'handler' | 'platform-configured' | 'platform-unmanaged'\n}\n\n/**\n * What the build tells the operator, once, per target.\n *\n * Silent degradation is the failure mode `rules/three-target-parity.md` exists\n * to prevent. Two things degrade quietly here and both are named rather than\n * discovered in production: a CSP that refuses inline scripts on a deploy while\n * allowing them locally, and an HTML document that never passes through the\n * handler these headers are attached to.\n *\n * The header names are read from the map the entry will actually carry, not\n * from a list written next to it. A configuration that switches HSTS or the CSP\n * off would otherwise be announced as sending them.\n */\nexport function describeDeployedSecurityHeaders(limits: DeployedSecurityHeaderLimits): string {\n const headers = buildSecurityHeaders(limits.securityHeaders ?? {}, { production: true })\n const names = Object.keys(headers)\n if (names.length === 0) {\n return ` ! \\`${limits.target}\\` sends no security headers: the configuration switched every one of them off.`\n }\n\n const lines = [\n ` ✓ security headers on every response \\`${limits.target}\\` returns: ${names.join(', ')}.`,\n ]\n const sendsCsp = names.some((name) => name.startsWith('Content-Security-Policy'))\n if (sendsCsp && !limits.mintsNonce) {\n lines.push(\n ` - The CSP carries no nonce: this target serves HTML written at build time,`,\n ` so there is no per-request value to put on a script tag. An inline`,\n ` <script> is refused by \\`script-src 'self'\\` here, while the same page`,\n ` under \\`theokit start\\` gets a nonce and runs it. Move inline scripts to`,\n ` \\`<script src=\"...\">\\`, or set \\`security.headers.cspMode: 'report-only'\\``,\n ` while you migrate.`,\n )\n }\n if (limits.documentHeaders === 'platform-configured') {\n lines.push(\n ` - The HTML document is served by the platform's static host, and reaches`,\n ` the browser with these headers through config this build emits. That`,\n ` path is not verified by a deploy from here — the values come from the`,\n ` same function the handler uses, but no response has been read back.`,\n )\n }\n if (limits.documentHeaders === 'platform-unmanaged') {\n lines.push(\n ` - The HTML document does NOT pass through this handler — the platform's`,\n ` static host serves it — so these headers reach \\`/api/*\\` responses and`,\n ` not the page. This build emits no artifact that could configure it, so`,\n ` set the document's headers on the platform (usetheokit/theokit#412).`,\n )\n }\n return lines.join('\\n')\n}\n"],"mappings":"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","names":[]}
package/dist/cli/index.js CHANGED
@@ -20,7 +20,7 @@ cli.command("dev", "Start development server").option("--port <port>", "Port num
20
20
  });
21
21
  cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
22
22
  try {
23
- const { buildCommand } = await import("../build-CZAM7K6J.js");
23
+ const { buildCommand } = await import("../build-RF2NLRHS.js");
24
24
  await buildCommand({ target: options.target });
25
25
  } catch (err) {
26
26
  const msg = err instanceof Error ? err.message : String(err);
@@ -44,7 +44,7 @@ cli.command("start", "Start production server").option("--port <port>", "Port nu
44
44
  });
45
45
  cli.command("preview", "Build for production, then serve it \u2014 one step (B-030)").option("--port <port>", "Port number").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
46
46
  try {
47
- const { previewCommand } = await import("../preview-3TLURUAL.js");
47
+ const { previewCommand } = await import("../preview-F6SR2JAQ.js");
48
48
  await previewCommand({
49
49
  port: options.port ? Number(options.port) : void 0,
50
50
  target: options.target
@@ -10,8 +10,9 @@ import {
10
10
  deployedRuntimeConfigFragment,
11
11
  deployedTraceFragment,
12
12
  describeDeployedSecurityHeaders,
13
- renderSecurityHeadersConfigLiteral
14
- } from "./chunk-VPX3MS5D.js";
13
+ renderSecurityHeadersConfigLiteral,
14
+ serverDirLiteral
15
+ } from "./chunk-PALZUV6E.js";
15
16
  import {
16
17
  findRootDiv
17
18
  } from "./chunk-3PWQQWT6.js";
@@ -148,7 +149,7 @@ function renderCloudflareWorkerEntry(opts = {}) {
148
149
  ``,
149
150
  `// CR-006: server directory is a build-time literal \u2014 Workers cannot`,
150
151
  `// call process.cwd() and resolving paths at runtime returned '/server'.`,
151
- `const serverDir = 'server'`,
152
+ `const serverDir = ${serverDirLiteral(opts)}`,
152
153
  ``,
153
154
  ...routeRuntimeLines(routeModuleEntries, routeTableEntries),
154
155
  `// #410 \u2014 the security baseline \`theokit start\` puts on every response, carried`,
@@ -332,4 +333,4 @@ export {
332
333
  renderCloudflareWorkerEntry,
333
334
  renderWranglerToml
334
335
  };
335
- //# sourceMappingURL=cloudflare-UYKOO6EP.js.map
336
+ //# sourceMappingURL=cloudflare-VIQX4FVC.js.map