theokit 0.2.4 → 0.4.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/{app-typed-client-US2ZXBEC.js → app-typed-client-62FYBLVJ.js} +3 -3
  2. package/dist/{app-typed-client-6GC6VWXS.js → app-typed-client-ITIYTBXO.js} +3 -3
  3. package/dist/{build-MREW6MVS.js → build-HIGHJQQV.js} +8 -8
  4. package/dist/{chunk-YWWHK7R6.js → chunk-2VQKDRVF.js} +2 -2
  5. package/dist/{chunk-2BQYEBCK.js → chunk-B6RP57M3.js} +84 -2
  6. package/dist/chunk-B6RP57M3.js.map +1 -0
  7. package/dist/{chunk-5OKZY2VL.js → chunk-BIGBFZSU.js} +10 -5
  8. package/dist/chunk-BIGBFZSU.js.map +1 -0
  9. package/dist/{chunk-X2OLD264.js → chunk-JAAHOGKI.js} +10 -5
  10. package/dist/chunk-JAAHOGKI.js.map +1 -0
  11. package/dist/{chunk-SNDZQ64K.js → chunk-OXIQI2XZ.js} +84 -2
  12. package/dist/chunk-OXIQI2XZ.js.map +1 -0
  13. package/dist/{chunk-BE2LKDI7.js → chunk-TRH2L3FI.js} +2 -2
  14. package/dist/cli/index.js +29 -5
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/{dev-6P2DM33Q.js → dev-266MVCVA.js} +6 -6
  17. package/dist/{dev-emit-PRSRPDHA.js → dev-emit-NO6OZJOQ.js} +3 -3
  18. package/dist/{dev-emit-MLDFOLUU.js → dev-emit-RBSFZZNO.js} +3 -3
  19. package/dist/{dist-E6BL4ZVY.js → dist-5V77Z223.js} +1 -1
  20. package/dist/index.js +2 -2
  21. package/dist/{openapi-SOKZTFE3.js → openapi-FNVSWZOL.js} +3 -3
  22. package/dist/{registry-5QFTLOL2.js → registry-4MZ26TZD.js} +1 -1
  23. package/dist/router-RGZFX75G.js +274 -0
  24. package/dist/router-RGZFX75G.js.map +1 -0
  25. package/dist/{routes-XVRAUH6H.js → routes-H4SPLFHJ.js} +2 -2
  26. package/dist/server/index.js +2 -2
  27. package/dist/server-routes-hmr-GZJGPWMS.js +44 -0
  28. package/dist/server-routes-hmr-GZJGPWMS.js.map +1 -0
  29. package/dist/server-routes-hmr-PBHSKCQJ.js +42 -0
  30. package/dist/server-routes-hmr-PBHSKCQJ.js.map +1 -0
  31. package/dist/{start-R7XSQL5L.js → start-HX3QUSOS.js} +5 -5
  32. package/dist/vite-plugin/index.js +2 -2
  33. package/dist/{vite-plugin-6MPHTKIW.js → vite-plugin-IK3NOWXS.js} +3 -3
  34. package/package.json +2 -2
  35. package/dist/chunk-2BQYEBCK.js.map +0 -1
  36. package/dist/chunk-5OKZY2VL.js.map +0 -1
  37. package/dist/chunk-SNDZQ64K.js.map +0 -1
  38. package/dist/chunk-X2OLD264.js.map +0 -1
  39. /package/dist/{app-typed-client-6GC6VWXS.js.map → app-typed-client-62FYBLVJ.js.map} +0 -0
  40. /package/dist/{app-typed-client-US2ZXBEC.js.map → app-typed-client-ITIYTBXO.js.map} +0 -0
  41. /package/dist/{build-MREW6MVS.js.map → build-HIGHJQQV.js.map} +0 -0
  42. /package/dist/{chunk-YWWHK7R6.js.map → chunk-2VQKDRVF.js.map} +0 -0
  43. /package/dist/{chunk-BE2LKDI7.js.map → chunk-TRH2L3FI.js.map} +0 -0
  44. /package/dist/{dev-6P2DM33Q.js.map → dev-266MVCVA.js.map} +0 -0
  45. /package/dist/{dev-emit-PRSRPDHA.js.map → dev-emit-NO6OZJOQ.js.map} +0 -0
  46. /package/dist/{dev-emit-MLDFOLUU.js.map → dev-emit-RBSFZZNO.js.map} +0 -0
  47. /package/dist/{dist-E6BL4ZVY.js.map → dist-5V77Z223.js.map} +0 -0
  48. /package/dist/{openapi-SOKZTFE3.js.map → openapi-FNVSWZOL.js.map} +0 -0
  49. /package/dist/{registry-5QFTLOL2.js.map → registry-4MZ26TZD.js.map} +0 -0
  50. /package/dist/{routes-XVRAUH6H.js.map → routes-H4SPLFHJ.js.map} +0 -0
  51. /package/dist/{start-R7XSQL5L.js.map → start-HX3QUSOS.js.map} +0 -0
  52. /package/dist/{vite-plugin-6MPHTKIW.js.map → vite-plugin-IK3NOWXS.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateManifest
3
- } from "./chunk-YWWHK7R6.js";
4
- import "./chunk-2BQYEBCK.js";
3
+ } from "./chunk-2VQKDRVF.js";
4
+ import "./chunk-B6RP57M3.js";
5
5
  import "./chunk-7TOMTMHT.js";
6
6
  import "./chunk-X2VVCJ4V.js";
7
7
  import {
@@ -278,4 +278,4 @@ export {
278
278
  emitClientDts,
279
279
  generateClientDts
280
280
  };
281
- //# sourceMappingURL=app-typed-client-US2ZXBEC.js.map
281
+ //# sourceMappingURL=app-typed-client-62FYBLVJ.js.map
@@ -2,10 +2,10 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  generateManifest
5
- } from "./chunk-BE2LKDI7.js";
5
+ } from "./chunk-TRH2L3FI.js";
6
6
  import {
7
7
  HTTP_METHODS
8
- } from "./chunk-SNDZQ64K.js";
8
+ } from "./chunk-OXIQI2XZ.js";
9
9
  import "./chunk-5OFPQK6N.js";
10
10
  import "./chunk-KT3MWNZG.js";
11
11
 
@@ -278,4 +278,4 @@ export {
278
278
  emitClientDts,
279
279
  generateClientDts
280
280
  };
281
- //# sourceMappingURL=app-typed-client-6GC6VWXS.js.map
281
+ //# sourceMappingURL=app-typed-client-ITIYTBXO.js.map
@@ -4,12 +4,12 @@ import {
4
4
  emitOpenApi,
5
5
  loadRoutesForOpenApi
6
6
  } from "./chunk-ZOXNJV2O.js";
7
- import {
8
- preflightNodeAndBindings
9
- } from "./chunk-F3TG5FJV.js";
10
7
  import {
11
8
  validateProjectStructure
12
9
  } from "./chunk-XMS5VRIK.js";
10
+ import {
11
+ preflightNodeAndBindings
12
+ } from "./chunk-F3TG5FJV.js";
13
13
  import {
14
14
  loadConfig,
15
15
  loadEnv
@@ -22,8 +22,8 @@ import {
22
22
  import {
23
23
  generateManifest,
24
24
  writeManifest as writeManifest2
25
- } from "./chunk-BE2LKDI7.js";
26
- import "./chunk-SNDZQ64K.js";
25
+ } from "./chunk-TRH2L3FI.js";
26
+ import "./chunk-OXIQI2XZ.js";
27
27
  import {
28
28
  walkSourceFiles
29
29
  } from "./chunk-5OFPQK6N.js";
@@ -520,7 +520,7 @@ async function buildCommand(options) {
520
520
  `);
521
521
  }
522
522
  async function runAdapterBuild(target, config, cwd) {
523
- const { theoPluginAsync } = await import("./vite-plugin-6MPHTKIW.js");
523
+ const { theoPluginAsync } = await import("./vite-plugin-IK3NOWXS.js");
524
524
  const { default: react } = await import("@vitejs/plugin-react");
525
525
  const ctx = {
526
526
  // `react()` may return Plugin or Plugin[] depending on version; spread the
@@ -528,7 +528,7 @@ async function runAdapterBuild(target, config, cwd) {
528
528
  // type updated to `Plugin[] | Promise<Plugin[]>`).
529
529
  makeVitePlugins: async (opts) => [react(), ...await theoPluginAsync(opts)].flat()
530
530
  };
531
- const { resolveAdapter } = await import("./registry-5QFTLOL2.js");
531
+ const { resolveAdapter } = await import("./registry-4MZ26TZD.js");
532
532
  const adapter = await resolveAdapter(target);
533
533
  await adapter.build(config, cwd, ctx);
534
534
  }
@@ -595,4 +595,4 @@ function relativize3(absPath, root) {
595
595
  export {
596
596
  buildCommand
597
597
  };
598
- //# sourceMappingURL=build-MREW6MVS.js.map
598
+ //# sourceMappingURL=build-HIGHJQQV.js.map
@@ -2,7 +2,7 @@ import {
2
2
  compilePattern,
3
3
  scanServerRoutes,
4
4
  scanWebSocketRoutes
5
- } from "./chunk-2BQYEBCK.js";
5
+ } from "./chunk-B6RP57M3.js";
6
6
  import {
7
7
  scanServerActions
8
8
  } from "./chunk-7TOMTMHT.js";
@@ -70,4 +70,4 @@ export {
70
70
  writeManifest,
71
71
  loadManifest
72
72
  };
73
- //# sourceMappingURL=chunk-YWWHK7R6.js.map
73
+ //# sourceMappingURL=chunk-2VQKDRVF.js.map
@@ -34,7 +34,7 @@ function matchRoute(url, routes) {
34
34
 
35
35
  // src/server/scan/scan.ts
36
36
  import { existsSync, statSync } from "fs";
37
- import { extname, join, relative } from "path";
37
+ import { basename, extname, join, relative } from "path";
38
38
 
39
39
  // src/server/scan/detect-http-methods.ts
40
40
  import { readFileSync } from "fs";
@@ -89,8 +89,88 @@ function detectExportedHttpMethods(filePath, content) {
89
89
  return [...found].sort();
90
90
  }
91
91
 
92
+ // src/server/scan/errors.ts
93
+ var ROUTER_MIGRATION_GUIDE_URL = "https://theokit.dev/migration/0.3-to-0.4-router";
94
+ var RouterConventionError = class extends Error {
95
+ name = "RouterConventionError";
96
+ file;
97
+ suggestion;
98
+ migrationUrl;
99
+ constructor(opts) {
100
+ const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL;
101
+ const message = [
102
+ `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,
103
+ ``,
104
+ ` File: ${opts.file}`,
105
+ ` Use directory-nested form: ${opts.suggestion}`,
106
+ ``,
107
+ `Migration guide: ${migrationUrl}`,
108
+ `Run \`theokit migrate router\` to convert all dotted basenames automatically.`
109
+ ].join("\n");
110
+ super(message);
111
+ this.file = opts.file;
112
+ this.suggestion = opts.suggestion;
113
+ this.migrationUrl = migrationUrl;
114
+ }
115
+ };
116
+
92
117
  // src/server/scan/scan.ts
93
118
  var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
119
+ var TEST_OR_SPEC_RE = /\.(test|spec)\.[jt]sx?$/;
120
+ function isTestOrSpecFile(filePath) {
121
+ return TEST_OR_SPEC_RE.test(basename(filePath));
122
+ }
123
+ function hasDotOutsideBrackets(segment) {
124
+ let depth = 0;
125
+ for (const ch of segment) {
126
+ if (ch === "[") depth++;
127
+ else if (ch === "]") depth--;
128
+ else if (ch === "." && depth === 0) return true;
129
+ }
130
+ return false;
131
+ }
132
+ function splitDottedSegmentOutsideBrackets(segment) {
133
+ const parts = [];
134
+ let current = "";
135
+ let depth = 0;
136
+ for (const ch of segment) {
137
+ if (ch === "[") {
138
+ depth++;
139
+ current += ch;
140
+ } else if (ch === "]") {
141
+ depth--;
142
+ current += ch;
143
+ } else if (ch === "." && depth === 0) {
144
+ if (current) parts.push(current);
145
+ current = "";
146
+ } else {
147
+ current += ch;
148
+ }
149
+ }
150
+ if (current) parts.push(current);
151
+ return parts;
152
+ }
153
+ function buildDirectoryNestedSuggestion(filePath, routesDir) {
154
+ const rel = relative(routesDir, filePath).replace(/\\/g, "/");
155
+ const ext = extname(rel);
156
+ const withoutExt = rel.slice(0, -ext.length);
157
+ const segments = withoutExt.split("/").flatMap(splitDottedSegmentOutsideBrackets);
158
+ return `routes/${segments.join("/")}${ext}`;
159
+ }
160
+ function assertNoDottedSegment(filePath, routesDir) {
161
+ const rel = relative(routesDir, filePath).replace(/\\/g, "/");
162
+ const ext = extname(rel);
163
+ const withoutExt = rel.slice(0, -ext.length);
164
+ const segments = withoutExt.split("/");
165
+ for (const seg of segments) {
166
+ if (hasDotOutsideBrackets(seg)) {
167
+ throw new RouterConventionError({
168
+ file: filePath,
169
+ suggestion: buildDirectoryNestedSuggestion(filePath, routesDir)
170
+ });
171
+ }
172
+ }
173
+ }
94
174
  function fileToRoutePath(filePath, routesDir) {
95
175
  let rel = relative(routesDir, filePath);
96
176
  const ext = extname(rel);
@@ -112,6 +192,8 @@ function scanServerRoutes(serverDir) {
112
192
  }
113
193
  const results = [];
114
194
  walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {
195
+ if (isTestOrSpecFile(absPath)) return;
196
+ assertNoDottedSegment(absPath, routesDir);
115
197
  const routePath = fileToRoutePath(absPath, routesDir);
116
198
  const { pattern, paramNames } = compilePattern(routePath);
117
199
  const methods = detectExportedHttpMethods(absPath);
@@ -174,4 +256,4 @@ export {
174
256
  scanServerRoutes,
175
257
  scanWebSocketRoutes
176
258
  };
177
- //# sourceMappingURL=chunk-2BQYEBCK.js.map
259
+ //# sourceMappingURL=chunk-B6RP57M3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/match.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/errors.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { RouterConventionError } from './errors.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const methods = detectExportedHttpMethods(absPath)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n // Sort: static first, then dynamic, then catch-all last\n const isCatchAll = (route: ServerRouteNode) => route.routePath.includes(':...')\n results.sort((a, b) => {\n const aStatic = a.paramNames.length === 0\n const bStatic = b.paramNames.length === 0\n const aCatchAll = isCatchAll(a)\n const bCatchAll = isCatchAll(b)\n\n // Static routes first\n if (aStatic && !bStatic) return -1\n if (!aStatic && bStatic) return 1\n // Catch-all routes last\n if (aCatchAll && !bCatchAll) return 1\n if (!aCatchAll && bCatchAll) return -1\n return a.routePath.localeCompare(b.routePath)\n })\n\n return results\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * We use `createRequire(import.meta.url)` to keep the package on its native\n * CJS path. The type-only namespace import gives us the AST helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\nconst require_ = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) &&\n hasExportModifier(ts.getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts.createSourceFile(\n filePath,\n src,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n return [...found].sort()\n}\n","/**\n * Router-convention errors thrown by the server-route scanner.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. See ADR-XXX (router-convention-decision) in CHANGELOG 0.4.0.\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\nexport interface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
@@ -31,7 +31,7 @@ import {
31
31
  matchRoute,
32
32
  scanServerRoutes,
33
33
  scanWebSocketRoutes
34
- } from "./chunk-2BQYEBCK.js";
34
+ } from "./chunk-B6RP57M3.js";
35
35
  import {
36
36
  scanServerActions
37
37
  } from "./chunk-7TOMTMHT.js";
@@ -1969,7 +1969,7 @@ async function theoPluginAsync(rootOrOptions) {
1969
1969
  })
1970
1970
  );
1971
1971
  }
1972
- const { appTypedClientPlugin } = await import("./app-typed-client-US2ZXBEC.js");
1972
+ const { appTypedClientPlugin } = await import("./app-typed-client-62FYBLVJ.js");
1973
1973
  const appClientPlugin = appTypedClientPlugin({
1974
1974
  cwd: projectRoot,
1975
1975
  serverDir: resolve6(projectRoot, "server"),
@@ -1980,12 +1980,17 @@ async function theoPluginAsync(rootOrOptions) {
1980
1980
  serverDir: resolve6(projectRoot, "server"),
1981
1981
  distDir: resolve6(projectRoot, ".theo")
1982
1982
  });
1983
+ const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-PBHSKCQJ.js");
1984
+ const routesHmrPlugin = serverRoutesHmrPlugin({
1985
+ serverDir: resolve6(projectRoot, "server")
1986
+ });
1983
1987
  return [
1984
1988
  theoPlugin(rootOrOptions),
1985
1989
  ...uiPlugins,
1986
1990
  ...servicesPlugins,
1987
1991
  appClientPlugin,
1988
- actionsPlugin
1992
+ actionsPlugin,
1993
+ routesHmrPlugin
1989
1994
  ];
1990
1995
  }
1991
1996
  function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
@@ -2238,7 +2243,7 @@ function theoPlugin(rootOrOptions) {
2238
2243
  server.watcher.on("unlink", handleRouteChange);
2239
2244
  if (resolvedOpenApi !== void 0) {
2240
2245
  const openApiCfg = resolvedOpenApi;
2241
- const { reEmitOpenApi } = await import("./dev-emit-MLDFOLUU.js");
2246
+ const { reEmitOpenApi } = await import("./dev-emit-RBSFZZNO.js");
2242
2247
  const openApiServerDir = resolve6(projectRoot, "server");
2243
2248
  const openApiDistDir = resolve6(projectRoot, resolvedDistDir);
2244
2249
  const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
@@ -2284,4 +2289,4 @@ export {
2284
2289
  theoPluginAsync,
2285
2290
  theoPlugin
2286
2291
  };
2287
- //# sourceMappingURL=chunk-5OKZY2VL.js.map
2292
+ //# sourceMappingURL=chunk-BIGBFZSU.js.map