theokit 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-virtual-module-IY46EEEX.js → actions-virtual-module-DL74V6SR.js} +2 -2
- package/dist/actions-virtual-module-DL74V6SR.js.map +1 -0
- package/dist/{actions-virtual-module-XNHDNCZ6.js → actions-virtual-module-ZDD54ZZE.js} +2 -2
- package/dist/actions-virtual-module-ZDD54ZZE.js.map +1 -0
- package/dist/{app-typed-client-OUJO3V5J.js → app-typed-client-4GIKUKRT.js} +4 -4
- package/dist/app-typed-client-4GIKUKRT.js.map +1 -0
- package/dist/{app-typed-client-YOMWSA3F.js → app-typed-client-4M4NVSK3.js} +3 -3
- package/dist/app-typed-client-4M4NVSK3.js.map +1 -0
- package/dist/{aws-lambda-GKSTX6HD.js → aws-lambda-SCRGTGF5.js} +5 -5
- package/dist/aws-lambda-SCRGTGF5.js.map +1 -0
- package/dist/{build-2WZ6XXVY.js → build-R74EU6BP.js} +11 -11
- package/dist/build-R74EU6BP.js.map +1 -0
- package/dist/{bun-ISHSNFIO.js → bun-FURRCQMS.js} +7 -7
- package/dist/bun-FURRCQMS.js.map +1 -0
- package/dist/{chunk-FI7MPTJW.js → chunk-3IOFTG3L.js} +1 -1
- package/dist/chunk-3IOFTG3L.js.map +1 -0
- package/dist/{chunk-U6TPSW4L.js → chunk-DUKXQNM7.js} +2 -2
- package/dist/{chunk-4S2UCILN.js → chunk-FGOX37NB.js} +8 -8
- package/dist/chunk-FGOX37NB.js.map +1 -0
- package/dist/{chunk-MR7OLIC6.js → chunk-GPF64ENM.js} +2 -2
- package/dist/{chunk-4S6YHNG2.js → chunk-GUUWDMR2.js} +2 -2
- package/dist/chunk-GUUWDMR2.js.map +1 -0
- package/dist/{chunk-WEGALZEU.js → chunk-HDA6M7P4.js} +3 -3
- package/dist/{chunk-K4M64WD6.js → chunk-IZQN5CDS.js} +4 -4
- package/dist/chunk-IZQN5CDS.js.map +1 -0
- package/dist/{chunk-M2EY7KDR.js → chunk-JD7AQNJS.js} +15 -15
- package/dist/chunk-JD7AQNJS.js.map +1 -0
- package/dist/{chunk-5Z2727GV.js → chunk-KJLECZWQ.js} +1 -1
- package/dist/chunk-KJLECZWQ.js.map +1 -0
- package/dist/{chunk-LC5PYNJP.js → chunk-MWYSRZI4.js} +7 -7
- package/dist/chunk-MWYSRZI4.js.map +1 -0
- package/dist/{chunk-VBZCVFSA.js → chunk-P2RS3WWY.js} +2 -2
- package/dist/{chunk-R7OC6UDK.js → chunk-P3SGM4NR.js} +1 -1
- package/dist/chunk-P3SGM4NR.js.map +1 -0
- package/dist/{chunk-ABVITXFH.js → chunk-S74FECKW.js} +1 -1
- package/dist/{chunk-ABVITXFH.js.map → chunk-S74FECKW.js.map} +1 -1
- package/dist/{chunk-COXLEZCN.js → chunk-UVHRD33F.js} +1 -1
- package/dist/chunk-UVHRD33F.js.map +1 -0
- package/dist/{chunk-27LWMYNK.js → chunk-X32KQH5C.js} +2 -2
- package/dist/chunk-X32KQH5C.js.map +1 -0
- package/dist/{chunk-2F4FROEQ.js → chunk-X3VNROZ7.js} +17 -17
- package/dist/chunk-X3VNROZ7.js.map +1 -0
- package/dist/{chunk-Y4H3JNVK.js → chunk-YB5DJJ4U.js} +1 -1
- package/dist/chunk-YB5DJJ4U.js.map +1 -0
- package/dist/cli/index.js +8 -8
- package/dist/{cloudflare-OJGJ7R2D.js → cloudflare-IZ6VJ2OU.js} +7 -7
- package/dist/cloudflare-IZ6VJ2OU.js.map +1 -0
- package/dist/{deno-deploy-BHWKFTOW.js → deno-deploy-O73NBXIW.js} +6 -6
- package/dist/deno-deploy-O73NBXIW.js.map +1 -0
- package/dist/{dev-U4UDZFDB.js → dev-ZU46C5AZ.js} +8 -8
- package/dist/{dev-emit-HHP2XJXE.js → dev-emit-66FCOS7V.js} +4 -4
- package/dist/{dev-emit-5VPRCHOD.js → dev-emit-JBVINGHU.js} +3 -3
- package/dist/devtools/entry.js +3 -4
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{docker-EZDA5FT7.js → docker-AZSJFLP2.js} +3 -3
- package/dist/docker-AZSJFLP2.js.map +1 -0
- package/dist/{index-DWWEdFh5.d.ts → index-CuHICqb9.d.ts} +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/{info-47VB62MV.js → info-CORLCPEJ.js} +4 -4
- package/dist/{netlify-GSNSJGMN.js → netlify-O7G3RLK4.js} +3 -3
- package/dist/netlify-O7G3RLK4.js.map +1 -0
- package/dist/{node-6I5B6RL3.js → node-LXPQLMVB.js} +3 -3
- package/dist/{openapi-NFLSNNVQ.js → openapi-TEOUOIJ2.js} +7 -7
- package/dist/registry-KW4F4D2L.js +25 -0
- package/dist/{routes-3A4XGIEJ.js → routes-GAXVWJUK.js} +6 -6
- package/dist/{schema-KZVOV333.js → schema-Z5VJ2I76.js} +3 -3
- package/dist/server/cron/index.js +2 -2
- package/dist/server/http/index.d.ts +1 -1
- package/dist/server/http/index.js +2 -2
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +18 -11
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +1 -1
- package/dist/server/jobs/index.js +2 -2
- package/dist/server/scan/index.d.ts +1 -1
- package/dist/server/scan/index.js +2 -2
- package/dist/{services-json-Z2QNGVPW.js → services-json-6SHWMYJH.js} +1 -1
- package/dist/services-json-6SHWMYJH.js.map +1 -0
- package/dist/{services-typed-client-T7M5VPBP.js → services-typed-client-ASZL7FQV.js} +2 -2
- package/dist/{start-QDRSRNXK.js → start-HGUNNF5X.js} +10 -10
- package/dist/start-HGUNNF5X.js.map +1 -0
- package/dist/{static-QI5NQT2X.js → static-EO73I4C7.js} +6 -6
- package/dist/static-EO73I4C7.js.map +1 -0
- package/dist/{theo-cloud-RSQCBNXL.js → theo-cloud-HBCYEODQ.js} +2 -2
- package/dist/theo-cloud-HBCYEODQ.js.map +1 -0
- package/dist/{vercel-TKPZK62A.js → vercel-GSFDMF7K.js} +4 -4
- package/dist/vercel-GSFDMF7K.js.map +1 -0
- package/dist/vite-plugin/index.js +3 -3
- package/dist/{vite-plugin-CR4JDFUQ.js → vite-plugin-TX5H4MB6.js} +8 -8
- package/package.json +1 -1
- package/dist/actions-virtual-module-IY46EEEX.js.map +0 -1
- package/dist/actions-virtual-module-XNHDNCZ6.js.map +0 -1
- package/dist/app-typed-client-OUJO3V5J.js.map +0 -1
- package/dist/app-typed-client-YOMWSA3F.js.map +0 -1
- package/dist/aws-lambda-GKSTX6HD.js.map +0 -1
- package/dist/build-2WZ6XXVY.js.map +0 -1
- package/dist/bun-ISHSNFIO.js.map +0 -1
- package/dist/chunk-27LWMYNK.js.map +0 -1
- package/dist/chunk-2F4FROEQ.js.map +0 -1
- package/dist/chunk-4S2UCILN.js.map +0 -1
- package/dist/chunk-4S6YHNG2.js.map +0 -1
- package/dist/chunk-5Z2727GV.js.map +0 -1
- package/dist/chunk-COXLEZCN.js.map +0 -1
- package/dist/chunk-FI7MPTJW.js.map +0 -1
- package/dist/chunk-K4M64WD6.js.map +0 -1
- package/dist/chunk-LC5PYNJP.js.map +0 -1
- package/dist/chunk-M2EY7KDR.js.map +0 -1
- package/dist/chunk-R7OC6UDK.js.map +0 -1
- package/dist/chunk-Y4H3JNVK.js.map +0 -1
- package/dist/cloudflare-OJGJ7R2D.js.map +0 -1
- package/dist/deno-deploy-BHWKFTOW.js.map +0 -1
- package/dist/docker-EZDA5FT7.js.map +0 -1
- package/dist/netlify-GSNSJGMN.js.map +0 -1
- package/dist/registry-QHEZ3BWB.js +0 -25
- package/dist/services-json-Z2QNGVPW.js.map +0 -1
- package/dist/start-QDRSRNXK.js.map +0 -1
- package/dist/static-QI5NQT2X.js.map +0 -1
- package/dist/theo-cloud-RSQCBNXL.js.map +0 -1
- package/dist/vercel-TKPZK62A.js.map +0 -1
- /package/dist/{chunk-U6TPSW4L.js.map → chunk-DUKXQNM7.js.map} +0 -0
- /package/dist/{chunk-MR7OLIC6.js.map → chunk-GPF64ENM.js.map} +0 -0
- /package/dist/{chunk-WEGALZEU.js.map → chunk-HDA6M7P4.js.map} +0 -0
- /package/dist/{chunk-VBZCVFSA.js.map → chunk-P2RS3WWY.js.map} +0 -0
- /package/dist/{dev-U4UDZFDB.js.map → dev-ZU46C5AZ.js.map} +0 -0
- /package/dist/{dev-emit-HHP2XJXE.js.map → dev-emit-66FCOS7V.js.map} +0 -0
- /package/dist/{dev-emit-5VPRCHOD.js.map → dev-emit-JBVINGHU.js.map} +0 -0
- /package/dist/{info-47VB62MV.js.map → info-CORLCPEJ.js.map} +0 -0
- /package/dist/{node-6I5B6RL3.js.map → node-LXPQLMVB.js.map} +0 -0
- /package/dist/{openapi-NFLSNNVQ.js.map → openapi-TEOUOIJ2.js.map} +0 -0
- /package/dist/{registry-QHEZ3BWB.js.map → registry-KW4F4D2L.js.map} +0 -0
- /package/dist/{routes-3A4XGIEJ.js.map → routes-GAXVWJUK.js.map} +0 -0
- /package/dist/{schema-KZVOV333.js.map → schema-Z5VJ2I76.js.map} +0 -0
- /package/dist/{services-typed-client-T7M5VPBP.js.map → services-typed-client-ASZL7FQV.js.map} +0 -0
- /package/dist/{vite-plugin-CR4JDFUQ.js.map → vite-plugin-TX5H4MB6.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/bun.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 { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { nodeAdapter } from './node.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(port: number, opts: { ssrStreaming?: boolean } = {}): string {\n const streamingComment = opts.ssrStreaming\n ? `// T2.3 — ssrStreaming on; renderStreamingWeb may be consumed by app code`\n : `// (ssrStreaming off)`\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 { randomUUID } from 'node:crypto'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\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, 'server')`,\n `const port = process.env.PORT ? Number(process.env.PORT) : ${port}`,\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 ` 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 ` const match = matchRoute(pathname, routes)`,\n ` if (!match) return notFoundResponse()`,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ` const requestId = randomUUID()`,\n ` const method = request.method.toUpperCase()`,\n ` await executeRoute({ route: match.route, method, params: match.params, req, res, loadModule, serverDir, requestId })`,\n ` return toResponse()`,\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 ``,\n `console.log('Theo (Bun) listening on http://localhost:' + port)`,\n ].join('\\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 entry = renderBunEntry(config.port, { ssrStreaming: config.ssrStreaming })\n const write =\n deps.writeEntry ??\n ((p, c) => {\n writeFileSync(p, c)\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')\n}\n\nexport const bunAdapter: DeployAdapter = {\n name: 'bun',\n build(config, cwd, ctx) {\n return buildBun(config, cwd, {}, ctx)\n },\n}\n"],"mappings":";;;;;;;;;;;;AAGA,SAAS,WAAW,qBAAqB;AACzC,SAAS,eAAe;AAcjB,SAAS,eAAe,MAAc,OAAmC,CAAC,GAAW;AAC1F,QAAM,mBAAmB,KAAK,eAC1B,mFACA;AACJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,8DAA8D,IAAI;AAAA,IAClE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,eAAsB,SACpB,QACA,KACA,OAAqB,CAAC,GACtB,KACe;AAEf,4BAA0B,OAAO,aAAa,GAAG,CAAC;AAElD,QAAM,eAAe,KAAK,gBAAgB,YAAY,MAAM,KAAK,WAAW;AAC5E,QAAM,aAAa,QAAQ,KAAK,GAAG;AAEnC,QAAM,YAAY,QAAQ,KAAK,cAAc;AAC7C,QAAM,YAAY,KAAK,cAAc,CAAC,MAAc,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACpF,YAAU,SAAS;AAEnB,QAAM,QAAQ,eAAe,OAAO,MAAM,EAAE,cAAc,OAAO,aAAa,CAAC;AAC/E,QAAM,QACJ,KAAK,eACJ,CAAC,GAAG,MAAM;AACT,kBAAc,GAAG,CAAC;AAAA,EACpB;AACF,QAAM,QAAQ,WAAW,YAAY,GAAG,KAAK;AAG7C,UAAQ,IAAI,wDAA8C;AAC5D;AAEO,IAAM,aAA4B;AAAA,EACvC,MAAM;AAAA,EACN,MAAM,QAAQ,KAAK,KAAK;AACtB,WAAO,SAAS,QAAQ,KAAK,CAAC,GAAG,GAAG;AAAA,EACtC;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/http/error-pages.ts","../src/server/http/static.ts"],"sourcesContent":["/**\n * T2.4 — Custom error pages loader.\n *\n * Loads optional `.theokit/client/404.html` and `500.html` from disk so adapters\n * (Node, CF, Vercel, Bun, Netlify, AWS Lambda, Deno) can pass them to the\n * shared `sendError` pipeline.\n *\n * EC-9: caps file size at 1MB. Files beyond the limit are skipped with a\n * console.warn and the default JSON error path is used.\n */\n\n/* eslint-disable security/detect-non-literal-fs-filename --\n * Loads `.theokit/client/404.html` and `500.html` from a directory `dir` that\n * is always the build output of the current project. The names are fixed\n * literals (`'404.html'` / `'500.html'`) — no HTTP input.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport const MAX_ERROR_HTML_BYTES = 1_048_576 // 1MB\n\nexport interface CustomErrorPages {\n custom404Html?: string\n custom500Html?: string\n}\n\nfunction loadIfSafe(dir: string, name: string): string | undefined {\n const path = resolve(dir, name)\n if (!existsSync(path)) return undefined\n try {\n const stat = statSync(path)\n if (stat.size > MAX_ERROR_HTML_BYTES) {\n console.warn(\n `[theokit] Custom error page ${name} is ${stat.size} bytes (max ${MAX_ERROR_HTML_BYTES}); skipping.`,\n )\n return undefined\n }\n return readFileSync(path, 'utf-8')\n } catch (err) {\n console.warn(`[theokit] Failed to read ${name}: ${(err as Error).message}`)\n return undefined\n }\n}\n\n/**\n * Load `.theokit/client/{404,500}.html` from the given client directory.\n * Returns object with both fields when present; missing pages remain undefined.\n */\nexport function loadCustomErrorPages(clientDir: string): CustomErrorPages {\n return {\n custom404Html: loadIfSafe(clientDir, '404.html'),\n custom500Html: loadIfSafe(clientDir, '500.html'),\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Static-file server. The URL path IS user-controlled, BUT before any fs\n * call we resolve to absolute and reject with 403 if the resolved path\n * escapes `clientDir` (see EC-1 guard at line below). The guard is\n * authoritative; the rule cannot see it.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { resolve, extname } from 'node:path'\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain',\n '.map': 'application/json',\n}\n\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n clientDir: string,\n): boolean {\n const urlPath = (req.url ?? '/').split('?')[0]\n\n // Path traversal prevention (EC-1)\n const filePath = resolve(clientDir, '.' + urlPath)\n if (!filePath.startsWith(clientDir)) {\n res.writeHead(403)\n res.end('Forbidden')\n return true\n }\n\n if (!existsSync(filePath)) return false\n\n const stat = statSync(filePath)\n if (!stat.isFile()) return false\n\n const ext = extname(filePath)\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'\n const content = readFileSync(filePath)\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': content.length,\n })\n res.end(content)\n return true\n}\n"],"mappings":";AAgBA,SAAS,YAAY,cAAc,gBAAgB;AACnD,SAAS,eAAe;AAEjB,IAAM,uBAAuB;AAOpC,SAAS,WAAW,KAAa,MAAkC;AACjE,QAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,OAAO,SAAS,IAAI;AAC1B,QAAI,KAAK,OAAO,sBAAsB;AACpC,cAAQ;AAAA,QACN,+BAA+B,IAAI,OAAO,KAAK,IAAI,eAAe,oBAAoB;AAAA,MACxF;AACA,aAAO;AAAA,IACT;AACA,WAAO,aAAa,MAAM,OAAO;AAAA,EACnC,SAAS,KAAK;AACZ,YAAQ,KAAK,4BAA4B,IAAI,KAAM,IAAc,OAAO,EAAE;AAC1E,WAAO;AAAA,EACT;AACF;AAMO,SAAS,qBAAqB,WAAqC;AACxE,SAAO;AAAA,IACL,eAAe,WAAW,WAAW,UAAU;AAAA,IAC/C,eAAe,WAAW,WAAW,UAAU;AAAA,EACjD;AACF;;;AC/CA,SAAS,cAAAA,aAAY,gBAAAC,eAAc,YAAAC,iBAAgB;AAEnD,SAAS,WAAAC,UAAS,eAAe;AAEjC,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,SAAS,gBACd,KACA,KACA,WACS;AACT,QAAM,WAAW,IAAI,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC;AAG7C,QAAM,WAAWA,SAAQ,WAAW,MAAM,OAAO;AACjD,MAAI,CAAC,SAAS,WAAW,SAAS,GAAG;AACnC,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,WAAW;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,CAACH,YAAW,QAAQ,EAAG,QAAO;AAElC,QAAM,OAAOE,UAAS,QAAQ;AAC9B,MAAI,CAAC,KAAK,OAAO,EAAG,QAAO;AAE3B,QAAM,MAAM,QAAQ,QAAQ;AAC5B,QAAM,cAAc,WAAW,GAAG,KAAK;AACvC,QAAM,UAAUD,cAAa,QAAQ;AAErC,MAAI,UAAU,KAAK;AAAA,IACjB,gBAAgB;AAAA,IAChB,kBAAkB,QAAQ;AAAA,EAC5B,CAAC;AACD,MAAI,IAAI,OAAO;AACf,SAAO;AACT;","names":["existsSync","readFileSync","statSync","resolve"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
theoConfigSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FGOX37NB.js";
|
|
6
6
|
|
|
7
7
|
// src/config/load-env.ts
|
|
8
8
|
import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
|
|
@@ -231,4 +231,4 @@ export {
|
|
|
231
231
|
loadEnv,
|
|
232
232
|
loadConfig
|
|
233
233
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
234
|
+
//# sourceMappingURL=chunk-DUKXQNM7.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
servicesConfigSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-X32KQH5C.js";
|
|
6
6
|
|
|
7
7
|
// src/config/schema.ts
|
|
8
8
|
import { z as z8 } from "zod";
|
|
@@ -164,7 +164,7 @@ var storageSchema = z7.object({
|
|
|
164
164
|
var theoConfigSchema = z8.object({
|
|
165
165
|
/**
|
|
166
166
|
* Plan v1.2 T2.1 — project identifier propagated into
|
|
167
|
-
* `.
|
|
167
|
+
* `.theokit/services.json` v2 `project` field. DNS-1123 compatible (drives
|
|
168
168
|
* Gitea repo + ArgoCD App naming on TheoCloud). When omitted, the build
|
|
169
169
|
* emits services.json v1 with the legacy `services-bundle` fallback
|
|
170
170
|
* (ADR D10) and logs a deprecation warning.
|
|
@@ -183,9 +183,9 @@ var theoConfigSchema = z8.object({
|
|
|
183
183
|
* `cleanOutDir` from wiping arbitrary locations (defense-in-depth on
|
|
184
184
|
* top of cleanOutDir's runtime EC-3 guard).
|
|
185
185
|
*/
|
|
186
|
-
distDir: z8.string().default(".
|
|
186
|
+
distDir: z8.string().default(".theokit").refine(
|
|
187
187
|
(d) => !/^([A-Za-z]:)?[/\\]/.test(d) && !d.startsWith(".."),
|
|
188
|
-
'distDir must be a relative path inside the project root (e.g., ".
|
|
188
|
+
'distDir must be a relative path inside the project root (e.g., ".theokit")'
|
|
189
189
|
),
|
|
190
190
|
/**
|
|
191
191
|
* Agent runtime configuration.
|
|
@@ -325,7 +325,7 @@ var theoConfigSchema = z8.object({
|
|
|
325
325
|
* opt in with defaults.
|
|
326
326
|
*
|
|
327
327
|
* Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
|
|
328
|
-
* "TheoKit App" · version "0.0.0" · outDir ".
|
|
328
|
+
* "TheoKit App" · version "0.0.0" · outDir ".theokit" (next to dist).
|
|
329
329
|
*
|
|
330
330
|
* The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
|
|
331
331
|
* `servers[].url` at emit time without rebuilding the config.
|
|
@@ -333,14 +333,14 @@ var theoConfigSchema = z8.object({
|
|
|
333
333
|
openapi: z8.object({
|
|
334
334
|
servers: z8.array(
|
|
335
335
|
z8.object({
|
|
336
|
-
url: z8.
|
|
336
|
+
url: z8.url(),
|
|
337
337
|
description: z8.string().optional()
|
|
338
338
|
})
|
|
339
339
|
).default([{ url: "http://localhost:3000", description: "Local development" }]),
|
|
340
340
|
specVersion: z8.enum(["3.1.0", "3.0.3"]).default("3.1.0"),
|
|
341
341
|
title: z8.string().default("TheoKit App"),
|
|
342
342
|
version: z8.string().default("0.0.0"),
|
|
343
|
-
outDir: z8.string().default(".
|
|
343
|
+
outDir: z8.string().default(".theokit")
|
|
344
344
|
}).optional(),
|
|
345
345
|
/**
|
|
346
346
|
* G5 T1.3 — error envelope transformer hook (blueprint ADR D3).
|
|
@@ -378,4 +378,4 @@ export {
|
|
|
378
378
|
storageSchema,
|
|
379
379
|
theoConfigSchema
|
|
380
380
|
};
|
|
381
|
-
//# sourceMappingURL=chunk-
|
|
381
|
+
//# sourceMappingURL=chunk-FGOX37NB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts","../src/config/schemas/header-safe.ts","../src/config/schemas/rate-limit.ts","../src/config/schemas/upload.ts","../src/config/schemas/logging.ts","../src/config/schemas/cache.ts","../src/config/schemas/security.ts","../src/config/schemas/storage.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { servicesConfigSchema } from '../services/index.js'\n\nimport {\n cacheSchema,\n loggingSchema,\n rateLimitSchema,\n securitySchema,\n storageSchema,\n uploadSchema,\n type FormatErrorContext,\n type FormatErrorHook,\n} from './schemas/index.js'\n\n// Re-export per-concern schemas + types so downstream consumers\n// (`adapters/*`, vite-plugin, generators, tests) keep their existing\n// imports valid. Per plan T2.3 — pure structural split; zero behavior\n// change at the call site.\nexport {\n cacheSchema,\n corsSchema,\n disallowedConfigSchema,\n headerSafeString,\n loggingSchema,\n rateLimitSchema,\n securityHeadersSchema,\n securitySchema,\n storageSchema,\n uploadSchema,\n} from './schemas/index.js'\n\nexport type { FormatErrorContext, FormatErrorHook, StorageConfig } from './schemas/index.js'\n\n/**\n * Root `theo.config.ts` schema — composer assembled from the per-concern\n * primitives re-exported above.\n *\n * Embedded blocks that exist ONLY as part of the root config (`agents`,\n * `ui`, `devtools`, `jobs`, `openapi`) stay inline below. They have no\n * external consumers; splitting them would create lonely files (M5\n * smell) without comprehension benefit.\n */\nexport const theoConfigSchema = z\n .object({\n /**\n * Plan v1.2 T2.1 — project identifier propagated into\n * `.theokit/services.json` v2 `project` field. DNS-1123 compatible (drives\n * Gitea repo + ArgoCD App naming on TheoCloud). When omitted, the build\n * emits services.json v1 with the legacy `services-bundle` fallback\n * (ADR D10) and logs a deprecation warning.\n */\n name: z\n .string()\n .max(63)\n .refine(\n // DNS-1123 equivalent expressed as explicit single-char anchors so\n // security/detect-unsafe-regex stays clean (no backtracking).\n (value) =>\n value.length > 0 &&\n /^[a-z0-9-]+$/u.test(value) &&\n !value.startsWith('-') &&\n !value.endsWith('-'),\n 'name must match DNS-1123 (lowercase alphanumeric+hyphens, 1-63 chars, no leading/trailing hyphen)',\n )\n .optional(),\n appDir: z.string().default('app'),\n serverDir: z.string().default('server'),\n /**\n * T2.2 / EC-4 — Build output directory. Must be a relative path inside\n * the project root. Refused absolute or parent-relative paths to prevent\n * `cleanOutDir` from wiping arbitrary locations (defense-in-depth on\n * top of cleanOutDir's runtime EC-3 guard).\n */\n distDir: z\n .string()\n .default('.theokit')\n .refine(\n (d) => !/^([A-Za-z]:)?[/\\\\]/.test(d) && !d.startsWith('..'),\n 'distDir must be a relative path inside the project root (e.g., \".theokit\")',\n ),\n /**\n * Agent runtime configuration.\n *\n * - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale\n * `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's\n * native registry GC but kept for backward-compat.\n * - `registry` (Phase 6, Production-Readiness #2): forwarded to\n * `Agent.registry.configure()` lazily on first request.\n * * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations\n * (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts\n * mid-stream → SDK aborts with code:'aborted'. Default 100 covers\n * indie/small-team; tune up for high-traffic.\n * * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables\n * idle eviction (LRU only).\n */\n agents: z\n .object({\n maxRegistries: z.number().int().positive().default(100),\n registry: z\n .object({\n /** LRU cap — MUST be ≥ max-concurrent-active-conversations. */\n maxAgents: z.number().int().positive().max(10_000).default(100),\n /** Idle eviction window in ms. 0 disables idle eviction (LRU only). */\n idleTimeoutMs: z\n .number()\n .int()\n .nonnegative()\n .default(30 * 60_000),\n })\n .optional(),\n })\n .optional(),\n port: z.number().int().min(1).max(65535).default(3000),\n ssr: z.boolean().default(false),\n /** When true (and ssr === true), use renderToPipeableStream with progressive\n * shell flush instead of single-shot renderToString. Opt-in for streaming\n * SSR; default false preserves the current behavior. */\n ssrStreaming: z.boolean().default(false),\n rateLimit: rateLimitSchema.optional(),\n upload: uploadSchema.optional(),\n logging: loggingSchema.optional(),\n security: securitySchema.optional(),\n serialization: z.enum(['json', 'superjson']).default('json'),\n // Plugins are validated structurally at runtime by createPluginRunnerFromConfig.\n // Zod only checks the shape minimally (must be array). Type-level safety is\n // provided through defineConfig at the user surface.\n plugins: z.array(z.unknown()).optional(),\n /** Enable client-side batching of theoFetch calls and the\n * /api/__theo_batch__ server endpoint. */\n batching: z\n .union([z.boolean(), z.object({ max: z.number().int().positive().optional() })])\n .optional(),\n /** T4.1 — Audit log. When `logger` is provided, framework events\n * (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are\n * emitted to it. Default: noop. */\n audit: z\n .object({\n logger: z.unknown().optional(),\n })\n .optional(),\n /** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;\n * undefined = enabled when @theokit/ui is detected in node_modules. */\n ui: z\n .union([\n z.literal(false),\n z.object({\n theme: z.enum(['violet-forge', 'noir', 'paper']).optional(),\n fonts: z.enum(['bundled', 'cdn']).optional(),\n }),\n ])\n .optional(),\n /**\n * Cache subsystem (caching-and-revalidation-plan).\n * Default `undefined` keeps caching disabled (backward compatible).\n * Pass `cache: {}` to opt in with defaults.\n */\n cache: cacheSchema.optional(),\n /**\n * Devtools overlay (Phase 0.4.0+ — see docs/plans/devtools-plan.md).\n *\n * - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.\n * - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).\n * - `{ ... }`: devtools enabled with explicit defaults (position, theme).\n *\n * Tree-shaken to noop in prod via the dual-export pattern in\n * `packages/theo/src/devtools/index.ts` (EC-17 positive prod check).\n */\n devtools: z\n .union([\n z.literal(false),\n z.object({\n position: z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']).optional(),\n theme: z.enum(['light', 'dark', 'system']).optional(),\n }),\n ])\n .optional(),\n /**\n * Informative list of deploy adapters the app supports. Does NOT\n * trigger per-build translations — only the `--target` CLI flag does\n * (EC-201 / ADR D2). If `config.adapters` includes a target NOT\n * matching `--target`, `theokit build` emits a cross-reference note.\n */\n adapters: z.array(z.string()).optional(),\n /**\n * Extra package names to pre-bundle via Vite's `optimizeDeps.include`.\n *\n * Framework auto-includes `@theokit/ui` and `lucide-react` when present.\n * Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`\n * (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers\n * installing `mermaid` for Mermaid SVG rendering — must declare those\n * here so Vite can resolve the literal in dev mode without\n * \"Failed to resolve module specifier\" errors. Production builds use\n * tsup/esbuild bundling and are not affected.\n *\n * Example:\n * viteOptimizeDeps: ['mermaid']\n */\n viteOptimizeDeps: z.array(z.string()).optional(),\n /**\n * Jobs backend (ADR D3 + T2.1). When configured, every request gets\n * `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a\n * `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).\n */\n jobs: z\n .object({\n backend: z.custom<unknown>((v) => typeof v === 'object' && v !== null),\n })\n .optional(),\n /**\n * StorageManager configuration (T1.1 / ADR-0007).\n *\n * Declares Postgres servers + databases + Redis servers. Consumed by\n * `getStorageManager().configure()` at boot via `start.ts`.\n *\n * Default `undefined` means manager exists but is not configured — adapters\n * relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will\n * throw with an actionable error on first use.\n */\n storage: storageSchema.optional(),\n /**\n * Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).\n *\n * Declarative external sidecar processes (Python FastAPI / Node Hono)\n * that boot alongside the TheoKit TS app. Empty `services: {}` is the\n * default and preserves Wave 1 behavior.\n */\n services: servicesConfigSchema,\n /**\n * G2 — OpenAPI emit (build-time only).\n *\n * When present, `theokit build` writes a generated `openapi.json` from\n * every `defineRoute()` body/query/params Zod schema. `undefined` keeps\n * the framework backward-compatible (no emit). Pass `openapi: {}` to\n * opt in with defaults.\n *\n * Defaults: spec 3.1.0 · servers `http://localhost:3000` · title\n * \"TheoKit App\" · version \"0.0.0\" · outDir \".theokit\" (next to dist).\n *\n * The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides\n * `servers[].url` at emit time without rebuilding the config.\n */\n openapi: z\n .object({\n servers: z\n .array(\n z.object({\n url: z.url(),\n description: z.string().optional(),\n }),\n )\n .default([{ url: 'http://localhost:3000', description: 'Local development' }]),\n specVersion: z.enum(['3.1.0', '3.0.3']).default('3.1.0'),\n title: z.string().default('TheoKit App'),\n version: z.string().default('0.0.0'),\n outDir: z.string().default('.theokit'),\n })\n .optional(),\n /**\n * G5 T1.3 — error envelope transformer hook (blueprint ADR D3).\n *\n * When present, runs at framework error-boundary time to enrich the\n * envelope with consumer-defined extensions (`hint`, custom telemetry\n * tags, etc.) BEFORE the envelope is serialized to the client. The\n * function signature is preserved via the explicit `FormatErrorHook`\n * type — TS inference flows from `theo.config.ts` into `@theo/client`\n * codegen and `@theokit/ui` AgentErrorCard consumers.\n *\n * Use `z.custom<FormatErrorHook>(...)` because Zod's built-in\n * `z.function()` discards its TS signature after parse; `z.custom`\n * with a function-typed runtime guard preserves the call-site type\n * without trading off Zod runtime validation.\n */\n formatError: z\n .custom<FormatErrorHook>((val) => typeof val === 'function', {\n message: 'formatError must be a function',\n })\n .optional(),\n })\n // EC-2 fix: cross-config refine — no service may share TheoKit's web port.\n .refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {\n message: 'service.port collides with TheoKit web port — change one of them',\n path: ['services'],\n })\n\nexport type TheoConfig = z.infer<typeof theoConfigSchema>\n\nexport type OpenApiConfig = NonNullable<TheoConfig['openapi']>\n\n// Silence unused-import warnings for type-only re-exports — TS strips at compile,\n// but exports above re-introduce them for downstream consumers.\nexport type _FormatErrorContext = FormatErrorContext\n","import { z } from 'zod'\n\n/**\n * EC-3 — CWE-113 HTTP Response Splitting mitigation.\n *\n * Every string that becomes a header value must reject CR/LF. Apps that\n * derive header values from untrusted input (feature flags, tenant config,\n * URL params) would otherwise let attackers inject Set-Cookie / Location\n * headers via `\\r\\n`. Apply this refinement to every header-bound string\n * field: permissionsPolicy, csp, hsts, referrerPolicy, CORS allow/expose.\n */\nexport const headerSafeString = z\n .string()\n .refine((s) => !/[\\r\\n]/.test(s), { message: 'Header value must not contain CR/LF' })\n","import { z } from 'zod'\n\n/**\n * Base bucket config — legacy shape preserved for backwards compatibility.\n */\nconst baseRateLimitSchema = z.object({\n windowMs: z.number().min(1),\n max: z.number().int().min(1),\n})\n\n/**\n * T2.2 — Per-route + per-user rate limit. The new shape adds `routes`\n * (path map), `keyBy`, and `cookieName`. The legacy flat shape is still\n * accepted via the union — see `createRouteRateLimiter` for normalization.\n */\nexport const rateLimitSchema = z.union([\n baseRateLimitSchema,\n z.object({\n default: baseRateLimitSchema.optional(),\n routes: z.record(z.string(), baseRateLimitSchema).optional(),\n keyBy: z\n .union([\n z.enum(['ip', 'session', 'user']),\n z.function({ input: z.tuple([z.unknown()]), output: z.string() }),\n ])\n .optional(),\n cookieName: z.string().min(1).optional(),\n }),\n])\n","import { z } from 'zod'\n\nexport const uploadSchema = z.object({\n maxFileSize: z\n .number()\n .min(1)\n .default(10 * 1024 * 1024), // 10MB\n maxFiles: z.number().int().min(1).default(10),\n maxFieldSize: z\n .number()\n .min(1)\n .default(1 * 1024 * 1024), // 1MB\n})\n","import { z } from 'zod'\n\nexport const loggingSchema = z.object({\n level: z.enum(['debug', 'info', 'warn', 'error', 'silent']).default('info'),\n})\n","import { z } from 'zod'\n\n/**\n * Cache subsystem config (caching-and-revalidation-plan).\n * Default `cache: undefined` keeps the framework backward-compatible\n * (no engine initialized → no cache primitives available).\n *\n * Setting `cache: {}` opts in with all defaults.\n */\nconst routeRuleSchema = z.object({\n maxAge: z.number().nonnegative().finite().optional(),\n swr: z.number().nonnegative().finite().optional(),\n tags: z.array(z.string()).optional(),\n})\n\nexport const cacheSchema = z.object({\n enabled: z.boolean().default(true),\n /** 'memory' uses InMemoryCacheAdapter; otherwise pass a custom adapter instance. */\n storage: z.union([z.literal('memory'), z.custom<unknown>()]).default('memory'),\n maxEntries: z.number().int().positive().default(1000),\n defaults: z\n .object({\n maxAge: z.number().nonnegative().finite().default(1),\n swr: z.number().nonnegative().finite().optional(),\n cacheErrors: z.boolean().default(false),\n })\n .default({ maxAge: 1, cacheErrors: false }),\n keyDerivation: z\n .object({\n excludeQuery: z.array(z.string()).optional(),\n sortQuery: z.boolean().default(true),\n lowercaseHost: z.boolean().default(true),\n })\n .default({ sortQuery: true, lowercaseHost: true }),\n routeRules: z.record(z.string(), routeRuleSchema).optional(),\n})\n","import { z } from 'zod'\n\nimport { headerSafeString } from './header-safe.js'\n\n/**\n * Phase 5 — CSRF warn-first (EC-1).\n *\n * 0.2.0 default: `warn`. Existing apps keep working but get structured\n * warnings about every state-mutating request that does not carry the\n * `X-Theo-Action: 1` header. 0.3.0 will flip the default to `strict`.\n *\n * Set explicitly to `strict` to opt into the future default early,\n * or `off` to disable CSRF entirely (only valid when you have another\n * defense — bearer auth, no session cookies, etc).\n */\n\n/**\n * Phase 6 — Default security headers (D4 / EC-2).\n *\n * 0.2.0 defaults:\n * - CSP in `report-only` mode (EC-2: don't break existing apps)\n * - X-Frame-Options: DENY · X-Content-Type-Options: nosniff\n * - Referrer-Policy: strict-origin-when-cross-origin\n * - HSTS in production only (no TLS on localhost)\n *\n * Users override individual headers, swap CSP to `enforce`, or disable\n * CSP entirely (`csp: false` / `cspMode: 'off'`).\n */\nexport const securityHeadersSchema = z.object({\n csp: z.union([headerSafeString, z.literal(false)]).optional(),\n // T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.\n // Users who want the old behaviour set `cspMode: 'report-only'`\n // explicitly. See docs/migrating/0.2-to-0.3.md.\n cspMode: z.enum(['enforce', 'report-only', 'off']).default('enforce'),\n hsts: z.union([headerSafeString, z.literal(false)]).optional(),\n frameOptions: z.enum(['DENY', 'SAMEORIGIN']).default('DENY'),\n contentTypeOptions: z.literal('nosniff').default('nosniff'),\n referrerPolicy: headerSafeString.default('strict-origin-when-cross-origin'),\n /**\n * T1.1 — Permissions-Policy directive string. EC-3-refined: rejects CR/LF.\n * Pass `false` to suppress the header.\n */\n permissionsPolicy: z.union([headerSafeString, z.literal(false)]).optional(),\n})\n\n/**\n * T5.1 — Disallowed-routes escalation pattern (Rails-inspired).\n *\n * `routes` accepts string (exact match — trailing slash matters) or\n * RegExp entries. Matched routes that would otherwise emit `csrf.warn`\n * dispatch through `disallowedBehavior` instead:\n * - `'warn'` : no-op vs the default warn-mode behavior\n * - `'raise'` : escalate to 403, even when global `csrf` mode is 'warn'\n *\n * Use to roll out strict mode per-route (e.g., flip /api/auth/* first)\n * without committing the entire surface to strict at once.\n */\nexport const disallowedConfigSchema = z.object({\n routes: z.array(z.union([z.string(), z.instanceof(RegExp)])),\n behavior: z.enum(['warn', 'raise']).default('raise'),\n})\n\n/**\n * T1.2 — CORS configuration.\n *\n * `origins` accepts a single value (`'*'`, string, RegExp, callback) OR an\n * array of (string | RegExp). The spec-violating `origins: '*'` +\n * `credentials: true` combination is rejected at parse time (browsers\n * ignore wildcards when credentials are sent).\n *\n * EC-3 — `allowedHeaders` and `exposedHeaders` entries go through the\n * header-safe refinement (CR/LF rejected — CWE-113 mitigation).\n */\nexport const corsSchema = z\n .object({\n origins: z.union([\n z.literal('*'),\n headerSafeString,\n z.instanceof(RegExp),\n z.array(z.union([headerSafeString, z.instanceof(RegExp)])),\n z.function({ input: z.tuple([z.string()]), output: z.boolean() }),\n ]),\n methods: z\n .array(z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']))\n .optional(),\n allowedHeaders: z.array(headerSafeString).optional(),\n exposedHeaders: z.array(headerSafeString).optional(),\n credentials: z.boolean().default(false),\n maxAge: z.number().int().min(0).max(86400).default(600),\n })\n .refine((c) => !(c.origins === '*' && c.credentials), {\n message: 'CORS spec forbids origins:\"*\" with credentials:true — browsers ignore the wildcard',\n })\n\nexport const securitySchema = z.object({\n // T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that\n // grep their warn-mode logs from 0.2.x already know which endpoints\n // break; opt back into 'warn' globally OR use disallowedRoutes for\n // surgical migration. See docs/migrating/0.2-to-0.3.md.\n csrf: z.enum(['off', 'warn', 'strict']).default('strict'),\n headers: securityHeadersSchema.optional(),\n /** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */\n disallowed: disallowedConfigSchema.optional(),\n /** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */\n cors: corsSchema.optional(),\n})\n","import { z } from 'zod'\n\n/**\n * StorageManager schemas (T1.1 / ADR-0007 D4).\n *\n * `theo.config.ts > storage` declares Postgres servers (credentials), the\n * databases on each server (TheoCloud / managed PG / self-host), and Redis\n * servers. The runtime `StorageManager` consumes this block; adapters\n * (PostgresJobBackend, PostgresConversationStorage, etc.) request pools by\n * database name and never see raw credentials at the call site.\n *\n * EC-1 (DOCUMENT in concept doc T4.1): Zod default strip-unknown mode\n * silently drops typo keys (`databasees` instead of `databases`). The\n * concept doc warns users to use exact names; runtime errors are still\n * actionable (`Database \"X\" not configured`).\n *\n * EC-2 (DEFERRED to use-time): `databases.X.server` references a key in\n * `servers`. Cross-field validation is intentionally NOT enforced at parse\n * time — `StorageManager.usePostgres()` throws an actionable error on the\n * first call with the dangling reference, matching Rails / Nitro behavior.\n */\nconst tlsConfigSchema = z.object({\n rejectUnauthorized: z.boolean().optional(),\n caCert: z.string().optional(),\n clientCert: z.string().optional(),\n clientKey: z.string().optional(),\n})\n\nconst serverConfigSchema = z.object({\n host: z.string().min(1),\n port: z.number().int().positive().max(65535).optional(),\n user: z.string().min(1),\n /** Password may be the empty string (some providers permit it). */\n password: z.string(),\n tls: tlsConfigSchema.optional(),\n})\n\nconst postgresPoolConfigSchema = z.object({\n min: z.number().int().nonnegative().optional(),\n max: z.number().int().positive().optional(),\n connectionTimeoutMillis: z.number().int().positive().optional(),\n idleTimeoutMillis: z.number().int().nonnegative().optional(),\n})\n\nconst postgresDatabaseConfigSchema = z.object({\n /** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */\n server: z.string().min(1),\n database: z.string().min(1),\n pool: postgresPoolConfigSchema.optional(),\n})\n\nconst redisServerConfigSchema = serverConfigSchema.extend({\n db: z.number().int().nonnegative().optional(),\n maxRetriesPerRequest: z.number().int().nonnegative().optional(),\n})\n\nexport const storageSchema = z.object({\n servers: z.record(z.string(), serverConfigSchema).optional(),\n databases: z.record(z.string(), postgresDatabaseConfigSchema).optional(),\n redis: z.record(z.string(), redisServerConfigSchema).optional(),\n})\n\nexport type StorageConfig = z.infer<typeof storageSchema>\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-OLPB36O2.js";
|
|
6
6
|
import {
|
|
7
7
|
scanServerActions
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-P3SGM4NR.js";
|
|
9
9
|
|
|
10
10
|
// src/server/scan/manifest.ts
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -70,4 +70,4 @@ export {
|
|
|
70
70
|
writeManifest,
|
|
71
71
|
loadManifest
|
|
72
72
|
};
|
|
73
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
//# sourceMappingURL=chunk-GPF64ENM.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
writeAtomic
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YB5DJJ4U.js";
|
|
4
4
|
import {
|
|
5
5
|
extractTraceContext,
|
|
6
6
|
generateNewTraceContext
|
|
@@ -523,4 +523,4 @@ export {
|
|
|
523
523
|
DuplicateJobNameError,
|
|
524
524
|
scanJobs
|
|
525
525
|
};
|
|
526
|
-
//# sourceMappingURL=chunk-
|
|
526
|
+
//# sourceMappingURL=chunk-GUUWDMR2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/jobs/define-job.ts","../src/server/jobs/job-backend.ts","../src/server/jobs/job-backend-memory.ts","../src/server/jobs/job-backend-postgres.ts","../src/server/jobs/job-runner.ts","../src/server/jobs/job-manifest.ts","../src/server/jobs/job-scan.ts"],"sourcesContent":["import type { JobDefinition, JobOptions } from './job-types.js'\n\nconst NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/\n\nfunction validateName(name: string): void {\n if (typeof name !== 'string' || !NAME_RE.test(name)) {\n throw new Error(\n `defineJob: invalid name \"${name}\". ` +\n 'Must be 1-64 chars, lowercase alphanumeric + hyphen, starting with [a-z0-9].',\n )\n }\n}\n\n/**\n * Declare a background job. Pure identity helper — no registration side\n * effect; the build-time scanner (T2.3) discovers definitions by walking\n * `server/jobs/` and emits `.theokit/jobs.json`.\n *\n * Per ADR-0003, handler returns `void` (or `Promise<void>`); no workflow\n * API. To run another job after this one, call `ctx.queue.enqueue` from\n * the handler.\n *\n * @example\n * ```ts\n * // server/jobs/process-document.ts\n * import { defineJob } from 'theokit/server'\n * import { z } from 'zod'\n *\n * export default defineJob('process-document', {\n * input: z.object({ documentId: z.string() }),\n * maxAttempts: 3,\n * async handler({ input, traceId }) {\n * // process input.documentId\n * },\n * })\n * ```\n */\nexport function defineJob<TInput = unknown>(\n name: string,\n options: JobOptions<TInput>,\n): JobDefinition<TInput> {\n validateName(name)\n return {\n name,\n maxAttempts: options.maxAttempts ?? 1,\n hasInputSchema: options.input !== undefined,\n handler: options.handler,\n inputSchema: options.input,\n }\n}\n","/**\n * Neutral `JobBackend` interface per ADR-0002.\n *\n * Implementations ship in core:\n * - `InMemoryJobBackend` (T2.2) — dev + tests; no external deps\n * - `PostgresJobBackend` (T3.1) — production; requires `pg` peer dep\n *\n * Community packages (`@theokit/jobs-redis`, `@theokit/jobs-sqs`, etc.)\n * implement this same interface to plug in alternate substrates without\n * modifying TheoKit core.\n */\n\nexport interface JobEnqueueInput {\n /** The job name (matches a `defineJob` declaration). */\n name: string\n /** Validated input (already passed through Zod or equivalent at the queue-client layer). */\n input: unknown\n /** Optional idempotency key for at-most-once dispatch within TTL. */\n idempotencyKey?: string\n /** Optional delay before the job becomes available for dispatch. */\n delaySeconds?: number\n /** W3C Trace Context propagation per ADR/R0.5.9. */\n traceparent?: string\n /** Maximum dispatch attempts (read from JobDefinition by the queue client). */\n maxAttempts?: number\n}\n\nexport interface JobLease {\n readonly jobId: string\n readonly name: string\n readonly input: unknown\n readonly attempts: number\n readonly maxAttempts: number\n readonly traceparent?: string\n readonly lockExpiresAt: Date\n}\n\nexport interface JobBackend {\n /** Human-readable name for logging (e.g., \"memory\", \"postgres\"). */\n readonly name: string\n\n /** Persist a job for later dispatch. Returns the generated jobId. */\n enqueue(input: JobEnqueueInput): Promise<{ jobId: string }>\n\n /** Worker loop polls for the next available leases. */\n dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]>\n\n /** Mark a job complete (success). */\n ack(jobId: string): Promise<void>\n\n /** Mark a job failed; backend decides retry vs DLQ via attempts. */\n nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void>\n\n /** Optional: return existing jobId if `key` was enqueued within `ttlSeconds`. */\n idempotency?(key: string, ttlSeconds: number): Promise<{ jobId: string } | null>\n}\n\n/**\n * Thrown from a job handler to opt out of retry policy. The backend MUST\n * nack with `nonRetryable: true` and permanently remove the job.\n */\nexport class NonRetryableError extends Error {\n readonly code = 'NON_RETRYABLE'\n constructor(message: string) {\n super(message)\n this.name = 'NonRetryableError'\n }\n}\n","// T5a.1a — Web Standards migration (leaf-first slice). Web Crypto's\n// `crypto.randomUUID()` is available on globalThis in every supported\n// runtime per ADR-0028 (Node 22+, CF Workers, Bun, Deno, browsers).\n// No node:crypto import needed.\n\nimport type { JobBackend, JobEnqueueInput, JobLease } from './job-backend.js'\n\n/**\n * In-memory job backend for dev + tests + single-instance prototypes.\n *\n * Zero external dependencies. Storage is two Maps:\n * - `pending: Map<jobId, PendingEntry>` — all enqueued jobs (locked or not)\n * - `idempotencyMap: Map<key, { jobId; expiresAt }>` — dedup window\n *\n * Per ADR-0002: this is one of two first-party backends shipped in core.\n * Production deploys plug in PostgresJobBackend (T3.1).\n *\n * EC-104 (jobs-crons-webhooks-cost-tracking-plan): registers a\n * `process.on('beforeExit')` handler that clears pending dispatch timers\n * and logs a warning with the dropped count. Failure mode is visible,\n * not silent. `destroy()` removes the listener for test isolation.\n */\n\nexport interface InMemoryJobBackendOptions {\n /** Maximum pending entries before oldest is dropped + warning logged. */\n maxPending?: number\n}\n\ninterface PendingEntry {\n jobId: string\n name: string\n input: unknown\n attempts: number\n maxAttempts: number\n traceparent?: string\n availableAt: number\n lockExpiresAt: number | null\n dispatchTimer: NodeJS.Timeout | null\n}\n\ninterface IdempotencyEntry {\n jobId: string\n expiresAt: number\n}\n\nconst DEFAULT_MAX_PENDING = 10_000\n\nexport class InMemoryJobBackend implements JobBackend {\n readonly name = 'memory'\n readonly #pending = new Map<string, PendingEntry>()\n readonly #idempotency = new Map<string, IdempotencyEntry>()\n readonly #maxPending: number\n #beforeExitHandler: (() => void) | null = null\n #destroyed = false\n\n constructor(opts: InMemoryJobBackendOptions = {}) {\n this.#maxPending = opts.maxPending ?? DEFAULT_MAX_PENDING\n // EC-104 — register graceful shutdown handler. We keep a reference\n // so `destroy()` can remove it (test isolation; avoid leaking\n // listeners across vitest test files).\n this.#beforeExitHandler = (): void => {\n this.#cleanupOnShutdown()\n }\n process.on('beforeExit', this.#beforeExitHandler)\n }\n\n async enqueue(input: JobEnqueueInput): Promise<{ jobId: string }> {\n // Overflow eviction (visible failure, not silent leak)\n if (this.#pending.size >= this.#maxPending) {\n const oldestKey = this.#pending.keys().next().value\n if (oldestKey) {\n const oldest = this.#pending.get(oldestKey)\n if (oldest?.dispatchTimer) clearTimeout(oldest.dispatchTimer)\n this.#pending.delete(oldestKey)\n }\n\n console.warn(\n `[theokit:jobs:memory] pending overflow at ${this.#maxPending} — dropped oldest. ` +\n 'Switch to PostgresJobBackend for production.',\n )\n }\n\n // Idempotency dedup\n if (input.idempotencyKey) {\n const existing = this.#idempotency.get(input.idempotencyKey)\n const now = Date.now()\n if (existing && existing.expiresAt > now) {\n return { jobId: existing.jobId }\n }\n }\n\n const jobId = globalThis.crypto.randomUUID()\n const now = Date.now()\n const availableAt = now + (input.delaySeconds ?? 0) * 1000\n const entry: PendingEntry = {\n jobId,\n name: input.name,\n input: input.input,\n attempts: 0,\n maxAttempts: input.maxAttempts ?? 1,\n traceparent: input.traceparent,\n availableAt,\n lockExpiresAt: null,\n dispatchTimer: null,\n }\n this.#pending.set(jobId, entry)\n\n if (input.idempotencyKey) {\n // Default TTL window matches typical idempotency expectations;\n // explicit idempotency(key, ttl) check overrides per caller.\n this.#idempotency.set(input.idempotencyKey, {\n jobId,\n expiresAt: now + 60_000,\n })\n }\n\n return Promise.resolve({ jobId })\n }\n\n async dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]> {\n const batchSize = opts.batchSize ?? 1\n const lockSeconds = opts.lockSeconds ?? 30\n const now = Date.now()\n\n const leases: JobLease[] = []\n for (const entry of this.#pending.values()) {\n if (leases.length >= batchSize) break\n // Skip if locked + lock still valid\n if (entry.lockExpiresAt !== null && entry.lockExpiresAt > now) continue\n // Skip if not yet available (delay)\n if (entry.availableAt > now) continue\n\n entry.lockExpiresAt = now + lockSeconds * 1000\n entry.attempts += 1\n leases.push({\n jobId: entry.jobId,\n name: entry.name,\n input: entry.input,\n attempts: entry.attempts,\n maxAttempts: entry.maxAttempts,\n traceparent: entry.traceparent,\n lockExpiresAt: new Date(entry.lockExpiresAt),\n })\n }\n return Promise.resolve(leases)\n }\n\n async ack(jobId: string): Promise<void> {\n const entry = this.#pending.get(jobId)\n if (entry?.dispatchTimer) clearTimeout(entry.dispatchTimer)\n this.#pending.delete(jobId)\n return Promise.resolve()\n }\n\n async nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void> {\n const entry = this.#pending.get(jobId)\n if (!entry) return Promise.resolve()\n if (opts.nonRetryable || entry.attempts >= entry.maxAttempts) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n this.#pending.delete(jobId)\n } else {\n // Release lock — becomes available again\n entry.lockExpiresAt = null\n }\n return Promise.resolve()\n }\n\n idempotency(key: string, ttlSeconds: number): Promise<{ jobId: string } | null> {\n const now = Date.now()\n const existing = this.#idempotency.get(key)\n if (!existing || existing.expiresAt <= now) {\n return Promise.resolve(null)\n }\n // Renew if caller's ttl differs\n existing.expiresAt = Math.max(existing.expiresAt, now + ttlSeconds * 1000)\n return Promise.resolve({ jobId: existing.jobId })\n }\n\n /**\n * Remove the beforeExit listener and clear all pending timers.\n * Idempotent — safe to call multiple times. Use in test teardown to\n * avoid leaking listeners across vitest test files.\n */\n destroy(): void {\n if (this.#destroyed) return\n this.#destroyed = true\n if (this.#beforeExitHandler) {\n process.off('beforeExit', this.#beforeExitHandler)\n this.#beforeExitHandler = null\n }\n for (const entry of this.#pending.values()) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n }\n }\n\n /** EC-104 test hook — invokes the shutdown handler synchronously. */\n triggerBeforeExitForTest(): void {\n this.#cleanupOnShutdown()\n }\n\n #cleanupOnShutdown(): void {\n const droppedCount = this.#pending.size\n for (const entry of this.#pending.values()) {\n if (entry.dispatchTimer) clearTimeout(entry.dispatchTimer)\n }\n if (droppedCount > 0) {\n console.warn(\n `[theokit:jobs:memory] ${droppedCount} jobs dropped on shutdown — ` +\n 'use PostgresJobBackend for durability across restarts.',\n )\n }\n }\n}\n","import type { PostgresFactory, StorageManager } from '../storage/storage-manager.js'\nimport type { PoolLike } from '../storage/storage-types.js'\n\nimport type { JobBackend, JobEnqueueInput, JobLease } from './job-backend.js'\n\n/**\n * Postgres-backed `JobBackend` per ADR-0002 (T3.1).\n *\n * Uses the Graphile Worker pattern:\n * - Single `theokit_jobs` table.\n * - Dequeue via `SELECT ... FOR UPDATE SKIP LOCKED` (concurrent workers\n * never race for the same job).\n * - Idempotency via UNIQUE INDEX on (name, idempotency_key).\n *\n * `pg` is an OPTIONAL peer dependency — the caller provides their own\n * `pg.Pool` (or compatible). This avoids forcing every TheoKit user to\n * install Postgres tooling for in-memory dev work.\n *\n * Pool size recommendation (EC-108): set `connectionTimeoutMillis` to\n * a finite value (e.g., 5000) so a saturated pool errors instead of\n * deadlocking. Suggested pool size: `min(workerConcurrency * 1.5, 20)`.\n *\n * @see https://github.com/graphile/worker (Postgres SKIP LOCKED reference)\n * @see docs/adr/0002-job-backend-interface-neutral-contract.md\n */\n\n/**\n * `PoolLike` moved to `../storage/storage-types.ts` (T0.2 / ADR-0007 D7) to\n * deduplicate the shape across multiple adapters. Re-exported here so existing\n * `import { PoolLike } from 'theokit/server'` (which routes through the jobs\n * barrel today) continues to compile unchanged.\n */\nexport type { PoolLike } from '../storage/storage-types.js'\n\nexport interface PostgresJobBackendOptions {\n pool: PoolLike\n /** Table name override (default `theokit_jobs`). */\n tableName?: string\n}\n\nconst DEFAULT_TABLE = 'theokit_jobs'\n\nexport class PostgresJobBackend implements JobBackend {\n readonly name = 'postgres'\n readonly #pool: PoolLike\n readonly #table: string\n\n constructor(opts: PostgresJobBackendOptions) {\n // Runtime guard for users who pass `pool: undefined` despite the type;\n // helps surface \"you forgot to install pg\" early with an actionable\n // error rather than a cryptic TypeError later.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!opts.pool) {\n throw new Error(\n 'PostgresJobBackend requires a `pool` option (pg.Pool or compatible). ' +\n 'Install pg via `pnpm add pg` and pass a configured Pool instance.',\n )\n }\n this.#pool = opts.pool\n this.#table = opts.tableName ?? DEFAULT_TABLE\n }\n\n /**\n * T2.1 — Construct a `PostgresJobBackend` using a pool managed by the\n * shared `StorageManager`. The manager caches the pool per `dbName`, so\n * repeated calls (in the same process) re-use the same connection pool.\n *\n * Equivalent to:\n * ```ts\n * const pool = manager.usePostgres(dbName, factory)\n * new PostgresJobBackend({ pool, ...options })\n * ```\n *\n * Errors thrown by the manager (unknown dbName, dangling server reference,\n * manager already disposed) propagate to the caller with actionable text.\n *\n * @see ADR-0007\n */\n static fromStorageManager(\n manager: StorageManager,\n dbName: string,\n factory: PostgresFactory,\n options?: Omit<PostgresJobBackendOptions, 'pool'>,\n ): PostgresJobBackend {\n const pool = manager.usePostgres(dbName, factory)\n return new PostgresJobBackend({ pool, ...options })\n }\n\n /**\n * Create or upgrade the schema. Idempotent (uses IF NOT EXISTS).\n * Call once at process start; safe to re-run on every deploy.\n */\n async migrate(): Promise<void> {\n const t = this.#table\n await this.#pool.query(`\n CREATE TABLE IF NOT EXISTS ${t} (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n input JSONB NOT NULL,\n attempts INT NOT NULL DEFAULT 0,\n max_attempts INT NOT NULL DEFAULT 1,\n available_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n locked_until TIMESTAMPTZ,\n traceparent TEXT,\n idempotency_key TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n )\n `)\n await this.#pool.query(`\n CREATE UNIQUE INDEX IF NOT EXISTS ${t}_idempotency_key_idx\n ON ${t} (name, idempotency_key)\n WHERE idempotency_key IS NOT NULL\n `)\n await this.#pool.query(`\n CREATE INDEX IF NOT EXISTS ${t}_available_at_idx\n ON ${t} (available_at)\n WHERE locked_until IS NULL\n `)\n }\n\n async enqueue(input: JobEnqueueInput): Promise<{ jobId: string }> {\n const id = randomId()\n const delaySeconds = input.delaySeconds ?? 0\n const t = this.#table\n\n // Idempotency: explicit SELECT-then-INSERT. The UNIQUE INDEX is the\n // safety net under race conditions (we surface the conflict by\n // re-selecting on insert-error). This 2-step path is pg-mem compatible\n // AND works on real Postgres.\n if (input.idempotencyKey) {\n const existing = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${t} WHERE name = $1 AND idempotency_key = $2 LIMIT 1`,\n [input.name, input.idempotencyKey],\n )\n if (existing.rows.length > 0) {\n return { jobId: existing.rows[0].id }\n }\n }\n\n await this.#pool.query(\n `INSERT INTO ${t} (id, name, input, max_attempts, available_at, traceparent, idempotency_key)\n VALUES ($1, $2, $3::jsonb, $4, NOW() + ($5 || ' seconds')::interval, $6, $7)`,\n [\n id,\n input.name,\n JSON.stringify(input.input),\n input.maxAttempts ?? 1,\n delaySeconds.toString(),\n input.traceparent ?? null,\n input.idempotencyKey ?? null,\n ],\n )\n return { jobId: id }\n }\n\n async dequeue(opts: { batchSize?: number; lockSeconds?: number }): Promise<JobLease[]> {\n const t = this.#table\n const batchSize = opts.batchSize ?? 1\n const lockSeconds = opts.lockSeconds ?? 30\n\n // Two-step pattern (pg-mem compatible AND real-Postgres compatible):\n // 1. SELECT FOR UPDATE SKIP LOCKED to claim candidate IDs.\n // 2. UPDATE in-place to mark lock_until + bump attempts.\n // On real Postgres, the FOR UPDATE SKIP LOCKED prevents concurrent\n // workers from seeing the same rows. On pg-mem the lock is silently\n // ignored, but pg-mem serializes all queries through one in-memory\n // db so the test still verifies single-claim semantics.\n const candidates = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${t}\n WHERE (locked_until IS NULL OR locked_until < NOW())\n AND available_at <= NOW()\n ORDER BY available_at ASC\n LIMIT $1\n FOR UPDATE SKIP LOCKED`,\n [batchSize],\n )\n\n if (candidates.rows.length === 0) return []\n\n const ids = candidates.rows.map((r) => r.id)\n // Build a parameterized IN clause: ($2, $3, ...). pg-mem's UPDATE\n // does not support `ANY($1::text[])`, so we materialize the list.\n // For batch sizes we expect (typically ≤100), this is fine.\n const placeholders = ids.map((_, i) => `$${i + 2}`).join(', ')\n\n const r = await this.#pool.query<{\n id: string\n name: string\n input: unknown\n attempts: number\n max_attempts: number\n traceparent: string | null\n locked_until: Date | string\n }>(\n `UPDATE ${t}\n SET locked_until = NOW() + ($1 || ' seconds')::interval,\n attempts = attempts + 1\n WHERE id IN (${placeholders})\n RETURNING id, name, input, attempts, max_attempts, traceparent, locked_until`,\n [lockSeconds.toString(), ...ids],\n )\n\n return r.rows.map((row) => ({\n jobId: row.id,\n name: row.name,\n input: row.input,\n attempts: row.attempts,\n maxAttempts: row.max_attempts,\n traceparent: row.traceparent ?? undefined,\n lockExpiresAt:\n row.locked_until instanceof Date ? row.locked_until : new Date(row.locked_until),\n }))\n }\n\n async ack(jobId: string): Promise<void> {\n await this.#pool.query(`DELETE FROM ${this.#table} WHERE id = $1`, [jobId])\n }\n\n async nack(jobId: string, opts: { error: string; nonRetryable?: boolean }): Promise<void> {\n const t = this.#table\n if (opts.nonRetryable) {\n await this.#pool.query(`DELETE FROM ${t} WHERE id = $1`, [jobId])\n return\n }\n // Check attempts >= maxAttempts → permanent removal.\n const r = await this.#pool.query<{\n attempts: number\n max_attempts: number\n }>(`SELECT attempts, max_attempts FROM ${t} WHERE id = $1`, [jobId])\n if (r.rows.length === 0) return\n const { attempts, max_attempts } = r.rows[0]\n if (attempts >= max_attempts) {\n await this.#pool.query(`DELETE FROM ${t} WHERE id = $1`, [jobId])\n } else {\n // Release lock — next dequeue picks it up again.\n await this.#pool.query(`UPDATE ${t} SET locked_until = NULL WHERE id = $1`, [jobId])\n }\n }\n\n async idempotency(key: string, _ttlSeconds: number): Promise<{ jobId: string } | null> {\n // The UNIQUE INDEX is the source of truth. Look for any non-deleted\n // row with this idempotency_key.\n const r = await this.#pool.query<{ id: string }>(\n `SELECT id FROM ${this.#table} WHERE idempotency_key = $1 LIMIT 1`,\n [key],\n )\n return r.rows.length > 0 ? { jobId: r.rows[0].id } : null\n }\n}\n\nfunction randomId(): string {\n // Use crypto.randomUUID if available; this is process-local, not DB-generated.\n // The DB UNIQUE constraint is on (name, idempotency_key), not on id, so\n // collisions across processes are functionally impossible.\n return globalThis.crypto.randomUUID()\n}\n","import {\n extractTraceContext,\n generateNewTraceContext,\n} from '../observability/trace-context-propagation.js'\n\nimport { type JobBackend, type JobLease, NonRetryableError } from './job-backend.js'\nimport type { JobContext, JobDefinition } from './job-types.js'\n\n/**\n * Job runner — pulls leases from a backend and invokes the matching\n * handler. Each invocation wires up `JobContext` with:\n * - `traceId` extracted from the lease's `traceparent` header (W3C\n * Trace Context per R0.5.9), OR a fresh generated trace_id when\n * the upstream had no traceparent OR it was malformed.\n * - `attempt` from the lease.\n * - `signal` that aborts when the lease times out.\n *\n * `tick()` runs one dequeue+dispatch cycle and returns when all leases\n * have been ack'd or nack'd. Production deploys call `tick()` in a loop\n * with backoff (the loop is outside this module's scope).\n *\n * EC-6 (trace continuity edge): when a lease has no traceparent OR a\n * malformed one, the handler still gets a valid traceId (generated) —\n * never null/undefined. The job MAY itself enqueue child jobs that\n * continue the new trace_id.\n */\n\nexport interface JobRunner {\n /** Run one dequeue+dispatch cycle. */\n tick(opts?: { batchSize?: number; lockSeconds?: number }): Promise<number>\n}\n\nexport function createJobRunner(\n backend: JobBackend,\n definitions: readonly JobDefinition[],\n): JobRunner {\n const byName = new Map<string, JobDefinition>()\n for (const def of definitions) {\n byName.set(def.name, def)\n }\n\n const runOne = async (lease: JobLease): Promise<void> => {\n const def = byName.get(lease.name)\n if (!def) {\n // Job name not registered in this runner's definition set. Nack\n // permanently — the lease can't be processed by this runner.\n await backend.nack(lease.jobId, {\n error: `No job definition for \"${lease.name}\"`,\n nonRetryable: true,\n })\n return\n }\n\n // Resolve trace ID from lease's traceparent, or fall back to fresh.\n let traceId: string\n if (lease.traceparent) {\n const headers = new Headers({ traceparent: lease.traceparent })\n const ctx = extractTraceContext(headers)\n traceId = ctx?.trace_id ?? generateNewTraceContext().trace_id\n } else {\n traceId = generateNewTraceContext().trace_id\n }\n\n // Optional Zod validation on input.\n let parsedInput: unknown = lease.input\n if (def.inputSchema) {\n try {\n parsedInput = def.inputSchema.parse(lease.input)\n } catch (err) {\n await backend.nack(lease.jobId, {\n error: `Input validation failed: ${err instanceof Error ? err.message : String(err)}`,\n nonRetryable: true,\n })\n return\n }\n }\n\n const abortController = new AbortController()\n // Auto-abort when lease expires\n const lockMs = lease.lockExpiresAt.getTime() - Date.now()\n const lockTimer = setTimeout(\n () => {\n abortController.abort()\n },\n Math.max(0, lockMs),\n )\n\n const jobCtx: JobContext = {\n traceId,\n input: parsedInput,\n signal: abortController.signal,\n attempt: lease.attempts,\n }\n\n try {\n await def.handler(jobCtx)\n await backend.ack(lease.jobId)\n } catch (err) {\n const isNonRetryable = err instanceof NonRetryableError\n await backend.nack(lease.jobId, {\n error: err instanceof Error ? err.message : String(err),\n nonRetryable: isNonRetryable,\n })\n } finally {\n clearTimeout(lockTimer)\n }\n }\n\n return {\n async tick(opts) {\n const leases = await backend.dequeue({\n batchSize: opts?.batchSize ?? 10,\n lockSeconds: opts?.lockSeconds ?? 30,\n })\n // Process leases sequentially — caller controls concurrency via\n // batchSize and tick frequency.\n for (const lease of leases) {\n await runOne(lease)\n }\n return leases.length\n },\n }\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { JobNode } from './job-scan.js'\n\nexport const JOB_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface JobManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport interface JobManifest {\n readonly schemaVersion: typeof JOB_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly jobs: readonly JobManifestEntry[]\n}\n\nexport function buildJobManifest(nodes: readonly JobNode[], projectRoot?: string): JobManifest {\n const jobs: JobManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n maxAttempts: n.maxAttempts,\n hasInputSchema: n.hasInputSchema,\n }))\n return {\n schemaVersion: JOB_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n jobs,\n }\n}\n\nexport function writeJobManifest(\n path: string,\n input: readonly JobNode[] | JobManifest,\n projectRoot?: string,\n): void {\n const manifest: JobManifest = isManifest(input) ? input : buildJobManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is JobManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'jobs' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\n\nimport { importUserModule } from '../../config/import-user-module.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { JobDefinition } from './job-types.js'\n\nexport interface JobNode {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport class DuplicateJobNameError extends Error {\n readonly code = 'DUPLICATE_JOB_NAME'\n constructor(\n public readonly jobName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate job name \"${jobName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Job names must be unique across server/jobs/.',\n )\n this.name = 'DuplicateJobNameError'\n }\n}\n\nconst JOB_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface JobModule {\n default?: unknown\n}\n\nfunction isJobDefinition(value: unknown): value is JobDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.handler === 'function' &&\n typeof def.maxAttempts === 'number' &&\n typeof def.hasInputSchema === 'boolean'\n )\n}\n\nexport async function scanJobs(jobsDir: string): Promise<JobNode[]> {\n if (!existsSync(jobsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(jobsDir, { extensions: JOB_EXTENSIONS }, (p) => {\n const base = basename(p)\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: JobNode[] = []\n for (const filePath of filePaths) {\n let mod: JobModule\n try {\n mod = await importUserModule(filePath)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import job file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isJobDefinition(exported)) {\n throw new Error(\n `Job file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineJob(name, { handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n maxAttempts: exported.maxAttempts,\n hasInputSchema: exported.hasInputSchema,\n })\n }\n\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateJobNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n"],"mappings":"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","names":["now","r"]}
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
compilePattern,
|
|
5
5
|
scanServerRoutes,
|
|
6
6
|
scanWebSocketRoutes
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-P2RS3WWY.js";
|
|
8
8
|
import {
|
|
9
9
|
scanServerActions
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UVHRD33F.js";
|
|
11
11
|
|
|
12
12
|
// src/server/scan/manifest.ts
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -72,4 +72,4 @@ export {
|
|
|
72
72
|
writeManifest,
|
|
73
73
|
loadManifest
|
|
74
74
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-HDA6M7P4.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
writeAtomic
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YB5DJJ4U.js";
|
|
4
4
|
import {
|
|
5
5
|
generateNewTraceContext
|
|
6
6
|
} from "./chunk-6NEXVBPY.js";
|
|
@@ -183,7 +183,7 @@ function convertToAwsCron(schedule) {
|
|
|
183
183
|
function translateCronToAws(serverlessYmlPath, crons) {
|
|
184
184
|
const cronFunctionsYaml = crons.map(
|
|
185
185
|
(c) => ` cron_${c.name}:
|
|
186
|
-
handler: .
|
|
186
|
+
handler: .theokit/server/crons/${c.name}.handler
|
|
187
187
|
events:
|
|
188
188
|
- schedule: ${convertToAwsCron(c.schedule)}`
|
|
189
189
|
).join("\n");
|
|
@@ -227,7 +227,7 @@ function translateCronToDeno(entryPath, crons) {
|
|
|
227
227
|
});`
|
|
228
228
|
);
|
|
229
229
|
const content = `// AUTOGENERATED by TheoKit cron build \u2014 DO NOT EDIT.
|
|
230
|
-
// Source: server/crons/*.ts \u2192 .
|
|
230
|
+
// Source: server/crons/*.ts \u2192 .theokit/crons.json \u2192 this file.
|
|
231
231
|
${lines.join("\n\n")}
|
|
232
232
|
`;
|
|
233
233
|
writeAtomic(entryPath, content);
|
|
@@ -386,4 +386,4 @@ export {
|
|
|
386
386
|
DuplicateCronNameError,
|
|
387
387
|
scanCrons
|
|
388
388
|
};
|
|
389
|
-
//# sourceMappingURL=chunk-
|
|
389
|
+
//# sourceMappingURL=chunk-IZQN5CDS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/cron/cron-validate.ts","../src/server/cron/define-cron.ts","../src/server/cron/cron-manifest.ts","../src/server/cron/adapter-translators.ts","../src/server/cron/cron-runtime-node.ts","../src/server/cron/cron-scan.ts"],"sourcesContent":["import { CronExpressionParser } from 'cron-parser'\n\n/**\n * Validate a cron schedule string per ADR-0004 — 5-field UTC strict.\n *\n * Accepts:\n * - Standard 5-field expressions: `minute hour dayOfMonth month dayOfWeek`\n * - Step (`*\\/15`), range (`1-5`), list (`MON,TUE,FRI`), wildcards\n *\n * Rejects:\n * - 6-field with seconds (`* * * * * *`)\n * - 7-field with year\n * - Shorthand (`@daily`, `@hourly`, `@yearly`, `@reboot`)\n * - Timezone suffix\n * - Empty / whitespace-only\n * - Malformed grammar\n *\n * Throws on every invalid input. Every error message includes the\n * original input and the fix.\n *\n * @see docs/adr/0004-cron-schedule-5-field-utc-strict.md\n */\nexport function validateCronSchedule(schedule: string): void {\n if (typeof schedule !== 'string') {\n throw new TypeError(`Invalid cron schedule: expected string, got ${typeof schedule}.`)\n }\n const trimmed = schedule.trim()\n if (trimmed.length === 0) {\n throw new Error(\n 'Invalid cron schedule: empty string. TheoKit cron uses 5-field UTC strict format, e.g. \"0 9 * * *\".',\n )\n }\n\n // Shorthand check BEFORE field counting — `@daily` is one \"field\" but\n // semantically not in the 5-field grammar.\n if (trimmed.startsWith('@')) {\n throw new Error(\n `Invalid cron schedule \"${schedule}\": shorthand not supported. ` +\n 'Use the equivalent 5-field form, e.g. \"@daily\" → \"0 0 * * *\", \"@hourly\" → \"0 * * * *\".',\n )\n }\n\n const parts = trimmed.split(/\\s+/)\n if (parts.length !== 5) {\n throw new Error(\n `Invalid cron schedule \"${schedule}\": expected 5 fields ` +\n '(\"minute hour dayOfMonth month dayOfWeek\"), ' +\n `got ${parts.length}. TheoKit treats all schedules as UTC; ` +\n 'for second-precision or timezone, see docs/concepts/crons.md.',\n )\n }\n\n // Final grammar validation via cron-parser. We pass `tz: 'UTC'`\n // explicitly so any embedded timezone hint surfaces as an error.\n try {\n CronExpressionParser.parse(trimmed, { tz: 'UTC' })\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Invalid cron schedule \"${schedule}\": ${reason}`)\n }\n}\n","import type { CronDefinition, CronOptions } from './cron-types.js'\nimport { validateCronSchedule } from './cron-validate.js'\n\nconst NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/\n\nfunction validateName(name: string): void {\n if (typeof name !== 'string' || !NAME_RE.test(name)) {\n throw new Error(\n `defineCron: invalid name \"${name}\". ` +\n 'Must be 1-64 chars, lowercase alphanumeric + hyphen, starting with [a-z0-9].',\n )\n }\n}\n\n/**\n * Declare a time-triggered handler. Pure identity helper — no\n * registration side effect; the build-time scanner (T1.3) discovers\n * definitions by walking `server/crons/` and emits a manifest the\n * adapters translate at deploy.\n *\n * @example\n * ```ts\n * // server/crons/morning-summary.ts\n * export default defineCron('morning-summary', {\n * schedule: '0 9 * * *', // 09:00 UTC\n * async handler({ traceId, scheduledAt, signal }) {\n * // ...\n * },\n * })\n * ```\n */\nexport function defineCron(name: string, options: CronOptions): CronDefinition {\n validateName(name)\n validateCronSchedule(options.schedule)\n return {\n name,\n schedule: options.schedule,\n handler: options.handler,\n concurrency: options.concurrency ?? 'forbid',\n }\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronNode } from './cron-scan.js'\n\nexport const CRON_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface CronManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: 'forbid' | 'allow'\n}\n\nexport interface CronManifest {\n readonly schemaVersion: typeof CRON_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly crons: readonly CronManifestEntry[]\n}\n\n/**\n * Build a `CronManifest` from scanned `CronNode[]`. Filepaths are kept\n * relative to the caller's project root for portability.\n */\nexport function buildCronManifest(nodes: readonly CronNode[], projectRoot?: string): CronManifest {\n const crons: CronManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n schedule: n.schedule,\n concurrency: n.concurrency,\n }))\n return {\n schemaVersion: CRON_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n crons,\n }\n}\n\n/**\n * Write the cron manifest to `path` atomically (EC-106).\n *\n * Accepts either a pre-built `CronManifest` OR an array of `CronNode[]`\n * (which gets converted via `buildCronManifest`). The atomic-write\n * helper guarantees `path` always contains valid JSON, even under\n * concurrent writes (e.g., dev-server rescan + build).\n */\nexport function writeCronManifest(\n path: string,\n input: readonly CronNode[] | CronManifest,\n projectRoot?: string,\n): void {\n const manifest: CronManifest = isManifest(input) ? input : buildCronManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is CronManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'crons' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time adapter translators: caller-controlled paths only.\n */\nimport { existsSync, readFileSync } from 'node:fs'\n\nimport { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronManifestEntry } from './cron-manifest.js'\n\n/**\n * Thrown when an existing platform-config file (vercel.json, wrangler.toml,\n * serverless.yml) cannot be parsed. Caller must fix the file before\n * re-running `theokit build`. We NEVER silently overwrite a user's config.\n */\nexport class ExistingConfigUnparseableError extends Error {\n readonly code = 'EXISTING_CONFIG_UNPARSEABLE'\n constructor(\n public readonly filePath: string,\n public readonly parseError: string,\n ) {\n super(\n `Existing config \"${filePath}\" could not be parsed: ${parseError}. ` +\n 'Fix or remove the file before re-running build — TheoKit never silently overwrites user configuration.',\n )\n this.name = 'ExistingConfigUnparseableError'\n }\n}\n\n// ──────────────────────────────────────────────────────────\n// Vercel — vercel.json crons[]\n// ──────────────────────────────────────────────────────────\n\ninterface VercelJson {\n crons?: { path: string; schedule: string }[]\n [key: string]: unknown\n}\n\n/**\n * Translate a TheoKit cron manifest into `vercel.json crons[]`.\n *\n * EC-105: existing fields (functions, headers, redirects, rewrites, env,\n * etc.) are preserved verbatim — ONLY the `crons[]` slice is replaced.\n */\nexport function translateCronToVercel(\n vercelJsonPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n let existing: VercelJson = {}\n if (existsSync(vercelJsonPath)) {\n const raw = readFileSync(vercelJsonPath, 'utf8')\n try {\n existing = JSON.parse(raw) as VercelJson\n } catch (err) {\n throw new ExistingConfigUnparseableError(\n vercelJsonPath,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n const merged: VercelJson = {\n ...existing,\n crons: crons.map((c) => ({\n path: `/api/__crons/${c.name}`,\n schedule: c.schedule,\n })),\n }\n writeAtomic(vercelJsonPath, JSON.stringify(merged, null, 2))\n}\n\n// ──────────────────────────────────────────────────────────\n// Cloudflare — wrangler.toml [triggers] crons\n// ──────────────────────────────────────────────────────────\n\n/**\n * Translate a TheoKit cron manifest into `wrangler.toml [triggers] crons`.\n *\n * EC-105: regex-based mutation that preserves comments + other sections\n * verbatim. Replaces only the `[triggers]` block (or appends if absent).\n */\nexport function translateCronToCloudflare(\n wranglerTomlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const schedules = crons.map((c) => `\"${c.schedule}\"`).join(', ')\n const triggersBlock = `[triggers]\\ncrons = [${schedules}]\\n`\n\n if (!existsSync(wranglerTomlPath)) {\n writeAtomic(wranglerTomlPath, triggersBlock)\n return\n }\n\n const existing = readFileSync(wranglerTomlPath, 'utf8')\n\n // Replace existing [triggers] section if present. Strategy: split into\n // line array, find the [triggers] header line, drop subsequent lines\n // until the next section header (line starting with `[`) or EOF.\n const lines = existing.split('\\n')\n const triggersIdx = lines.findIndex((l) => l.trim() === '[triggers]')\n\n let next: string\n if (triggersIdx !== -1) {\n let endIdx = lines.length\n for (let i = triggersIdx + 1; i < lines.length; i++) {\n if (lines[i].trim().startsWith('[')) {\n endIdx = i\n break\n }\n }\n const before = lines.slice(0, triggersIdx).join('\\n')\n const after = lines.slice(endIdx).join('\\n')\n next = `${before}\\n${triggersBlock.trimEnd()}\\n${after}`.replace(/\\n{3,}/g, '\\n\\n')\n } else {\n next = existing.endsWith('\\n')\n ? `${existing}\\n${triggersBlock}`\n : `${existing}\\n\\n${triggersBlock}`\n }\n writeAtomic(wranglerTomlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// AWS Lambda — serverless.yml functions.<fn>.events: schedule\n// ──────────────────────────────────────────────────────────\n\n/**\n * Convert TheoKit's 5-field UTC cron to AWS EventBridge's 6-field format.\n * EventBridge requires `?` in EITHER day-of-month OR day-of-week (not both `*`).\n *\n * Algorithm:\n * - If DOM is \"*\" and DOW is \"*\" → insert ? in DOW (default)\n * - If DOM is \"*\" and DOW is specific → insert ? in DOM\n * - If DOM is specific and DOW is \"*\" → insert ? in DOW\n * - Append \"*\" year field at end.\n */\nexport function convertToAwsCron(schedule: string): string {\n const parts = schedule.trim().split(/\\s+/)\n if (parts.length !== 5) {\n throw new Error(`Invalid 5-field schedule for AWS conversion: \"${schedule}\"`)\n }\n const [minute, hour, dom, month, dow] = parts\n let awsDom = dom\n let awsDow = dow\n if (dom === '*' && dow === '*') {\n awsDow = '?'\n } else if (dom === '*') {\n awsDom = '?'\n } else if (dow === '*') {\n awsDow = '?'\n }\n return `cron(${minute} ${hour} ${awsDom} ${month} ${awsDow} *)`\n}\n\n/**\n * Translate a TheoKit cron manifest into a `serverless.yml` functions\n * map with `events: - schedule: cron(...)` entries.\n *\n * EC-105: appends to `functions:` block, preserving all existing\n * functions/sections. Cron functions are named `cron_<name>` to avoid\n * collision with user-declared functions.\n */\nexport function translateCronToAws(\n serverlessYmlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const cronFunctionsYaml = crons\n .map(\n (c) => ` cron_${c.name}:\n handler: .theokit/server/crons/${c.name}.handler\n events:\n - schedule: ${convertToAwsCron(c.schedule)}`,\n )\n .join('\\n')\n\n const block = `\\nfunctions:\\n${cronFunctionsYaml}\\n`\n\n if (!existsSync(serverlessYmlPath)) {\n // Minimal serverless.yml scaffold\n writeAtomic(\n serverlessYmlPath,\n `service: theokit-app\\nprovider:\\n name: aws\\n runtime: nodejs22.x\\n${block}`,\n )\n return\n }\n\n const existing = readFileSync(serverlessYmlPath, 'utf8')\n\n // Look for an existing top-level `functions:` block; if present, append our\n // cron entries under it (don't replace user-declared functions).\n const functionsRe = /^functions:\\s*\\n/m\n let next: string\n if (functionsRe.test(existing)) {\n next = existing.replace(functionsRe, (match) => `${match}${cronFunctionsYaml}\\n`)\n } else {\n next = existing.endsWith('\\n') ? `${existing}${block}` : `${existing}\\n${block}`\n }\n writeAtomic(serverlessYmlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// Deno Deploy — Deno.cron registrations\n// ──────────────────────────────────────────────────────────\n\n/**\n * Emit a Deno entry file that registers each cron via `Deno.cron`.\n *\n * Unlike Vercel/CF/AWS, Deno.cron is an in-process runtime API — the\n * entry file is a managed artifact (overwritten each build), not a user\n * config (so EC-105 doesn't apply here).\n */\nexport function translateCronToDeno(entryPath: string, crons: readonly CronManifestEntry[]): void {\n const lines = crons.map(\n (c) =>\n `Deno.cron(\"${c.name}\", \"${c.schedule}\", async () => {\n const mod = await import(\"../${c.filePath}\");\n const def = mod.default;\n await def.handler({\n traceId: crypto.randomUUID().replace(/-/g, \"\"),\n scheduledAt: new Date(),\n signal: AbortSignal.timeout(60_000),\n });\n});`,\n )\n const content = `// AUTOGENERATED by TheoKit cron build — DO NOT EDIT.\n// Source: server/crons/*.ts → .theokit/crons.json → this file.\n${lines.join('\\n\\n')}\n`\n writeAtomic(entryPath, content)\n}\n","import { CronExpressionParser } from 'cron-parser'\n\nimport { generateNewTraceContext } from '../observability/trace-context-propagation.js'\n\nimport type { CronContext, CronDefinition } from './cron-types.js'\n\n/**\n * In-memory cron scheduler for `theokit dev` (T1.4).\n *\n * Algorithm:\n * - For each cron, compute `nextFireAt = cron-parser.next()`.\n * - Schedule a `setTimeout(handler, nextFireAt - now)`.\n * - After handler invocation (sync return or Promise scheduled), recompute\n * next fire from CURRENT time (drift-free vs scheduled time).\n *\n * Per-cron isolation (EC-109):\n * - Each cron's handler invocation is fire-and-forget (`void` scheduled).\n * A hanging handler does NOT block the scheduler loop nor other crons.\n * - `concurrency: 'forbid'` (default) tracks an in-flight flag per-cron;\n * subsequent ticks skip + warn while the in-flight flag is set.\n * - `concurrency: 'allow'` runs handlers concurrently — caller's responsibility.\n *\n * Production deploys use platform-native triggers (T1.5 adapter translators);\n * this scheduler exists only for local dev iteration.\n */\n\nexport interface CronScheduler {\n start(): void\n stop(): void\n}\n\ninterface CronJobState {\n readonly def: CronDefinition\n inFlight: boolean\n timer: NodeJS.Timeout | null\n abortController: AbortController | null\n}\n\nexport function createCronScheduler(definitions: readonly CronDefinition[]): CronScheduler {\n const states: CronJobState[] = definitions.map((def) => ({\n def,\n inFlight: false,\n timer: null,\n abortController: null,\n }))\n\n let started = false\n\n const fireAndReschedule = (state: CronJobState, scheduledAt: Date): void => {\n state.timer = null\n\n if (state.inFlight && state.def.concurrency === 'forbid') {\n console.warn(\n `[theokit:cron] \"${state.def.name}\" skipped tick at ${scheduledAt.toISOString()} ` +\n '— previous handler still running (concurrency: forbid).',\n )\n scheduleNext(state)\n return\n }\n\n state.inFlight = true\n state.abortController = new AbortController()\n const traceCtx = generateNewTraceContext()\n const ctx: CronContext = {\n traceId: traceCtx.trace_id,\n scheduledAt,\n signal: state.abortController.signal,\n }\n\n // Fire-and-forget: EC-109 — never await here so a hanging handler\n // can't block the scheduler loop or other crons.\n void Promise.resolve()\n .then(() => state.def.handler(ctx))\n .catch((err: unknown) => {\n console.error(\n `[theokit:cron] \"${state.def.name}\" handler error:`,\n err instanceof Error ? err.message : err,\n )\n })\n .finally(() => {\n state.inFlight = false\n })\n\n scheduleNext(state)\n }\n\n const scheduleNext = (state: CronJobState): void => {\n if (!started) return\n const interval = CronExpressionParser.parse(state.def.schedule, {\n tz: 'UTC',\n currentDate: new Date(),\n })\n const next = interval.next().toDate()\n const delayMs = Math.max(0, next.getTime() - Date.now())\n state.timer = setTimeout(() => {\n fireAndReschedule(state, next)\n }, delayMs)\n // setTimeout returns a Timeout object; in Node, .unref() is available but\n // we DO want this to keep the event loop alive in dev — explicit no-unref.\n }\n\n return {\n start(): void {\n if (started) return\n started = true\n for (const state of states) {\n scheduleNext(state)\n }\n },\n stop(): void {\n started = false\n for (const state of states) {\n if (state.timer) {\n clearTimeout(state.timer)\n state.timer = null\n }\n state.abortController?.abort()\n state.abortController = null\n }\n },\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only. No HTTP\n * input ever reaches these fs calls.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\n\nimport { importUserModule } from '../../config/import-user-module.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { CronConcurrencyPolicy, CronDefinition } from './cron-types.js'\n\n/**\n * One discovered cron from build-time scan. The handler is intentionally\n * NOT included — manifest is platform-neutral and consumed by adapters\n * that emit static config. Runtime dispatch loads the handler at fire time.\n */\nexport interface CronNode {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: CronConcurrencyPolicy\n}\n\nexport class DuplicateCronNameError extends Error {\n readonly code = 'DUPLICATE_CRON_NAME'\n constructor(\n public readonly cronName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate cron name \"${cronName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Cron names must be unique across server/crons/.',\n )\n this.name = 'DuplicateCronNameError'\n }\n}\n\nconst CRON_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface CronModule {\n default?: unknown\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.schedule === 'string' &&\n typeof def.handler === 'function' &&\n (def.concurrency === 'forbid' || def.concurrency === 'allow')\n )\n}\n\n/**\n * Scan a directory for cron definition files and return the discovered\n * `CronNode[]` sorted by name. Throws `DuplicateCronNameError` on name\n * collision and `Error` on missing default export.\n *\n * Sequential by design — module imports are awaited one-by-one to keep\n * error messages anchored to the file that failed.\n */\nexport async function scanCrons(cronsDir: string): Promise<CronNode[]> {\n if (!existsSync(cronsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(cronsDir, { extensions: CRON_EXTENSIONS }, (p) => {\n const base = basename(p)\n // Skip private helpers (`_helper.ts`) and OS junk (`.DS_Store`).\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: CronNode[] = []\n for (const filePath of filePaths) {\n let mod: CronModule\n try {\n mod = await importUserModule(filePath)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import cron file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n schedule: exported.schedule,\n concurrency: exported.concurrency,\n })\n }\n\n // Dup-name detection\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateCronNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n"],"mappings":"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","names":["CronExpressionParser","CronExpressionParser","existsSync","existsSync"]}
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
resolveTransformer,
|
|
14
14
|
safeAudit,
|
|
15
15
|
sendError
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-S74FECKW.js";
|
|
17
17
|
import {
|
|
18
18
|
loadConfig
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-DUKXQNM7.js";
|
|
20
20
|
import {
|
|
21
21
|
buildServicesProxyConfig
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-X32KQH5C.js";
|
|
23
23
|
import {
|
|
24
24
|
broadcastToDevtools
|
|
25
25
|
} from "./chunk-5ODOE6EF.js";
|
|
@@ -31,10 +31,10 @@ import {
|
|
|
31
31
|
matchRoute,
|
|
32
32
|
scanServerRoutes,
|
|
33
33
|
scanWebSocketRoutes
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-P2RS3WWY.js";
|
|
35
35
|
import {
|
|
36
36
|
scanServerActions
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-UVHRD33F.js";
|
|
38
38
|
|
|
39
39
|
// src/vite-plugin/index.ts
|
|
40
40
|
import { existsSync as existsSync7 } from "fs";
|
|
@@ -67,7 +67,7 @@ function runConfigHook(ctx) {
|
|
|
67
67
|
ignored: [
|
|
68
68
|
"**/referencias/**",
|
|
69
69
|
"**/.theokit/**",
|
|
70
|
-
"**/.
|
|
70
|
+
"**/.theokit/**",
|
|
71
71
|
"**/dist/**",
|
|
72
72
|
"**/node_modules/**"
|
|
73
73
|
]
|
|
@@ -210,7 +210,7 @@ var DEFAULT_RESOLVED = {
|
|
|
210
210
|
auditLogger: void 0,
|
|
211
211
|
devtoolsEnabled: true,
|
|
212
212
|
openapi: void 0,
|
|
213
|
-
distDir: ".
|
|
213
|
+
distDir: ".theokit"
|
|
214
214
|
};
|
|
215
215
|
async function resolvePluginConfig(projectRoot) {
|
|
216
216
|
try {
|
|
@@ -1145,7 +1145,7 @@ async function runConfigureServer(server, ctx) {
|
|
|
1145
1145
|
server.watcher.on("unlink", handleRouteChange);
|
|
1146
1146
|
if (ctx.resolvedOpenApi !== void 0) {
|
|
1147
1147
|
const openApiCfg = ctx.resolvedOpenApi;
|
|
1148
|
-
const { reEmitOpenApi } = await import("./dev-emit-
|
|
1148
|
+
const { reEmitOpenApi } = await import("./dev-emit-66FCOS7V.js");
|
|
1149
1149
|
const openApiServerDir = resolve4(ctx.projectRoot, "server");
|
|
1150
1150
|
const openApiDistDir = resolve4(ctx.projectRoot, ctx.resolvedDistDir);
|
|
1151
1151
|
const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
|
|
@@ -2117,7 +2117,7 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
2117
2117
|
});
|
|
2118
2118
|
const servicesPlugins = [];
|
|
2119
2119
|
if (options.services && Object.keys(options.services).length > 0) {
|
|
2120
|
-
const { servicesTypedClientPlugin } = await import("./services-typed-client-
|
|
2120
|
+
const { servicesTypedClientPlugin } = await import("./services-typed-client-ASZL7FQV.js");
|
|
2121
2121
|
servicesPlugins.push(
|
|
2122
2122
|
servicesTypedClientPlugin({
|
|
2123
2123
|
cwd: projectRoot,
|
|
@@ -2125,16 +2125,16 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
2125
2125
|
})
|
|
2126
2126
|
);
|
|
2127
2127
|
}
|
|
2128
|
-
const { appTypedClientPlugin } = await import("./app-typed-client-
|
|
2128
|
+
const { appTypedClientPlugin } = await import("./app-typed-client-4GIKUKRT.js");
|
|
2129
2129
|
const appClientPlugin = appTypedClientPlugin({
|
|
2130
2130
|
cwd: projectRoot,
|
|
2131
2131
|
serverDir: resolve7(projectRoot, "server"),
|
|
2132
|
-
distDir: resolve7(projectRoot, ".
|
|
2132
|
+
distDir: resolve7(projectRoot, ".theokit")
|
|
2133
2133
|
});
|
|
2134
|
-
const { actionsVirtualModule } = await import("./actions-virtual-module-
|
|
2134
|
+
const { actionsVirtualModule } = await import("./actions-virtual-module-DL74V6SR.js");
|
|
2135
2135
|
const actionsPlugin = actionsVirtualModule({
|
|
2136
2136
|
serverDir: resolve7(projectRoot, "server"),
|
|
2137
|
-
distDir: resolve7(projectRoot, ".
|
|
2137
|
+
distDir: resolve7(projectRoot, ".theokit")
|
|
2138
2138
|
});
|
|
2139
2139
|
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-GZJGPWMS.js");
|
|
2140
2140
|
const routesHmrPlugin = serverRoutesHmrPlugin({
|
|
@@ -2195,7 +2195,7 @@ function theoPlugin(rootOrOptions) {
|
|
|
2195
2195
|
let auditLogger;
|
|
2196
2196
|
let devtoolsEnabled = true;
|
|
2197
2197
|
let resolvedOpenApi;
|
|
2198
|
-
let resolvedDistDir = ".
|
|
2198
|
+
let resolvedDistDir = ".theokit";
|
|
2199
2199
|
const isDevModeRef = { value: false };
|
|
2200
2200
|
let configLoadedOnce = false;
|
|
2201
2201
|
return {
|
|
@@ -2297,4 +2297,4 @@ export {
|
|
|
2297
2297
|
theoPluginAsync,
|
|
2298
2298
|
theoPlugin
|
|
2299
2299
|
};
|
|
2300
|
-
//# sourceMappingURL=chunk-
|
|
2300
|
+
//# sourceMappingURL=chunk-JD7AQNJS.js.map
|