theokit 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/actions-virtual-module-E6PU47GV.js +175 -0
  2. package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
  3. package/dist/actions-virtual-module-E7IELVHL.js +174 -0
  4. package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
  5. package/dist/app-typed-client-4L4L4BWD.js +289 -0
  6. package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
  7. package/dist/app-typed-client-6PAT6VOL.js +289 -0
  8. package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
  9. package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
  10. package/dist/build-EWZDEDT3.js.map +1 -0
  11. package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
  12. package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
  13. package/dist/build-request-body-preview-II2235E6.js +54 -0
  14. package/dist/build-request-body-preview-II2235E6.js.map +1 -0
  15. package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
  16. package/dist/chunk-2BQYEBCK.js.map +1 -0
  17. package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
  18. package/dist/chunk-4W447H7M.js.map +1 -0
  19. package/dist/chunk-7DHGV77J.js +140 -0
  20. package/dist/chunk-7DHGV77J.js.map +1 -0
  21. package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
  22. package/dist/chunk-C27AZN46.js.map +1 -0
  23. package/dist/chunk-ERAZNHHR.js +73 -0
  24. package/dist/chunk-ERAZNHHR.js.map +1 -0
  25. package/dist/chunk-F3TG5FJV.js +97 -0
  26. package/dist/chunk-F3TG5FJV.js.map +1 -0
  27. package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
  28. package/dist/chunk-KJVEJILQ.js.map +1 -0
  29. package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
  30. package/dist/chunk-M23FP2AS.js.map +1 -0
  31. package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
  32. package/dist/chunk-MOT42NAH.js +165 -0
  33. package/dist/chunk-MOT42NAH.js.map +1 -0
  34. package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
  35. package/dist/chunk-OJHO2X7C.js.map +1 -0
  36. package/dist/chunk-QLGES2HC.js +189 -0
  37. package/dist/chunk-QLGES2HC.js.map +1 -0
  38. package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
  39. package/dist/chunk-SEVYARIU.js.map +1 -0
  40. package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
  41. package/dist/chunk-TFECHXR5.js.map +1 -0
  42. package/dist/chunk-WSJKACWB.js +17 -0
  43. package/dist/chunk-WSJKACWB.js.map +1 -0
  44. package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
  45. package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
  46. package/dist/chunk-ZOXNJV2O.js +358 -0
  47. package/dist/chunk-ZOXNJV2O.js.map +1 -0
  48. package/dist/cli/index.js +22 -7
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/client/index.d.ts +31 -1
  51. package/dist/client/index.js +100 -0
  52. package/dist/client/index.js.map +1 -1
  53. package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
  54. package/dist/dev-74KPI5C5.js.map +1 -0
  55. package/dist/dev-emit-3CHM73TV.js +390 -0
  56. package/dist/dev-emit-3CHM73TV.js.map +1 -0
  57. package/dist/dev-emit-I3CKOHML.js +44 -0
  58. package/dist/dev-emit-I3CKOHML.js.map +1 -0
  59. package/dist/devtools/entry.js +545 -181
  60. package/dist/devtools/entry.js.map +1 -1
  61. package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
  62. package/dist/dispatcher-E6QV32BO.js +83 -0
  63. package/dist/dispatcher-E6QV32BO.js.map +1 -0
  64. package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
  65. package/dist/dist-R2Q7GVDW.js.map +1 -0
  66. package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
  67. package/dist/generate-DT4IIAHF.js.map +1 -0
  68. package/dist/index.d.ts +108 -1
  69. package/dist/index.js +5 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
  72. package/dist/openapi-CBASKC7W.js +84 -0
  73. package/dist/openapi-CBASKC7W.js.map +1 -0
  74. package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
  75. package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
  76. package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
  77. package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
  78. package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
  79. package/dist/server/cost/index.js +1 -1
  80. package/dist/server/index.d.ts +219 -47
  81. package/dist/server/index.js +35 -116
  82. package/dist/server/index.js.map +1 -1
  83. package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
  84. package/dist/start-5JTFWF4T.js.map +1 -0
  85. package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
  86. package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
  87. package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
  88. package/dist/vite-plugin/index.d.ts +25 -1
  89. package/dist/vite-plugin/index.js +5 -2
  90. package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
  91. package/package.json +4 -2
  92. package/dist/build-IQQYCA3B.js.map +0 -1
  93. package/dist/chunk-FRBYJANT.js.map +0 -1
  94. package/dist/chunk-GQSTRXXB.js.map +0 -1
  95. package/dist/chunk-JBC7PFV4.js.map +0 -1
  96. package/dist/chunk-JZ5WEG3S.js.map +0 -1
  97. package/dist/chunk-OEB3OIYP.js.map +0 -1
  98. package/dist/chunk-OK2PJOLK.js.map +0 -1
  99. package/dist/chunk-WQCPKA2L.js.map +0 -1
  100. package/dist/chunk-ZK5JCE5J.js.map +0 -1
  101. package/dist/dev-VN6WS3CM.js.map +0 -1
  102. package/dist/dist-Q5VBFWVL.js.map +0 -1
  103. package/dist/generate-SIO4JSKJ.js.map +0 -1
  104. package/dist/start-ZGWW5MOL.js.map +0 -1
  105. package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
  106. /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
  107. /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
  108. /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
  109. /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
  110. /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
  111. /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
  112. /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-3EQP2JYM.js.map} +0 -0
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+ import {
4
+ walkSourceFiles
5
+ } from "./chunk-MOT42NAH.js";
6
+
7
+ // src/core/contracts/http-methods.ts
8
+ var HTTP_METHODS = [
9
+ "GET",
10
+ "POST",
11
+ "PUT",
12
+ "PATCH",
13
+ "DELETE",
14
+ "HEAD",
15
+ "OPTIONS"
16
+ ];
17
+ var HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase());
18
+
19
+ // src/server/scan/scan.ts
20
+ import { existsSync, statSync } from "fs";
21
+ import { extname, join, relative } from "path";
22
+
23
+ // src/server/scan/detect-http-methods.ts
24
+ import { readFileSync } from "fs";
25
+ import { createRequire } from "module";
26
+ var require_ = createRequire(import.meta.url);
27
+ var ts = require_("typescript");
28
+ var HTTP_METHOD_NAMES = new Set(HTTP_METHODS);
29
+ function hasExportModifier(modifiers) {
30
+ if (!modifiers) return false;
31
+ for (const m of modifiers) {
32
+ if (m.kind === ts.SyntaxKind.ExportKeyword) return true;
33
+ }
34
+ return false;
35
+ }
36
+ function collectFromStatement(stmt, found) {
37
+ if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {
38
+ for (const decl of stmt.declarationList.declarations) {
39
+ if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {
40
+ found.add(decl.name.text);
41
+ }
42
+ }
43
+ return;
44
+ }
45
+ if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && hasExportModifier(ts.getModifiers(stmt))) {
46
+ if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {
47
+ found.add(stmt.name.text);
48
+ }
49
+ return;
50
+ }
51
+ if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
52
+ for (const spec of stmt.exportClause.elements) {
53
+ if (HTTP_METHOD_NAMES.has(spec.name.text)) {
54
+ found.add(spec.name.text);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ function detectExportedHttpMethods(filePath, content) {
60
+ const src = content ?? readFileSync(filePath, "utf-8");
61
+ const sourceFile = ts.createSourceFile(
62
+ filePath,
63
+ src,
64
+ ts.ScriptTarget.Latest,
65
+ /* setParentNodes */
66
+ false,
67
+ ts.ScriptKind.TS
68
+ );
69
+ const found = /* @__PURE__ */ new Set();
70
+ for (const stmt of sourceFile.statements) {
71
+ collectFromStatement(stmt, found);
72
+ }
73
+ return [...found].sort();
74
+ }
75
+
76
+ // src/server/scan/match.ts
77
+ function compilePattern(routePath) {
78
+ const paramNames = [];
79
+ const regexStr = routePath.replace(/:(?:\.\.\.)?([^/]+)/g, (match, name) => {
80
+ paramNames.push(name);
81
+ return match.startsWith(":...") ? "(.+)" : "([^/]+)";
82
+ });
83
+ return { pattern: new RegExp(`^${regexStr}$`), paramNames };
84
+ }
85
+ function matchRoute(url, routes) {
86
+ let path = url.split("?")[0];
87
+ if (path.length > 1 && path.endsWith("/")) {
88
+ path = path.slice(0, -1);
89
+ }
90
+ for (const route of routes) {
91
+ const match = route.pattern.exec(path);
92
+ if (match) {
93
+ const params = {};
94
+ route.paramNames.forEach((name, i) => {
95
+ params[name] = match[i + 1];
96
+ });
97
+ return { route, params };
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ // src/server/scan/scan.ts
104
+ var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
105
+ function fileToRoutePath(filePath, routesDir) {
106
+ let rel = relative(routesDir, filePath);
107
+ const ext = extname(rel);
108
+ rel = rel.slice(0, -ext.length);
109
+ rel = rel.replace(/\\/g, "/");
110
+ if (rel.endsWith("/index")) {
111
+ rel = rel.slice(0, -6);
112
+ } else if (rel === "index") {
113
+ rel = "";
114
+ }
115
+ rel = rel.replace(/\[\.\.\.([^\]]+)\]/g, ":...$1");
116
+ rel = rel.replace(/\[([^\]]+)\]/g, ":$1");
117
+ return `/api/${rel}`;
118
+ }
119
+ function scanServerRoutes(serverDir) {
120
+ const routesDir = join(serverDir, "routes");
121
+ if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {
122
+ return [];
123
+ }
124
+ const results = [];
125
+ walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {
126
+ const routePath = fileToRoutePath(absPath, routesDir);
127
+ const { pattern, paramNames } = compilePattern(routePath);
128
+ const methods = detectExportedHttpMethods(absPath);
129
+ results.push({
130
+ filePath: absPath,
131
+ routePath,
132
+ paramNames,
133
+ pattern,
134
+ methods
135
+ });
136
+ });
137
+ const conflicting = results.find((r) => r.routePath === "/api/__theo_batch__");
138
+ if (conflicting) {
139
+ throw new Error(
140
+ `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.`
141
+ );
142
+ }
143
+ const isCatchAll = (route) => route.routePath.includes(":...");
144
+ results.sort((a, b) => {
145
+ const aStatic = a.paramNames.length === 0;
146
+ const bStatic = b.paramNames.length === 0;
147
+ const aCatchAll = isCatchAll(a);
148
+ const bCatchAll = isCatchAll(b);
149
+ if (aStatic && !bStatic) return -1;
150
+ if (!aStatic && bStatic) return 1;
151
+ if (aCatchAll && !bCatchAll) return 1;
152
+ if (!aCatchAll && bCatchAll) return -1;
153
+ return a.routePath.localeCompare(b.routePath);
154
+ });
155
+ return results;
156
+ }
157
+
158
+ // src/server/scan/ws-scan.ts
159
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
160
+ import { extname as extname2, join as join2, relative as relative2 } from "path";
161
+ var WS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
162
+ function scanWebSocketRoutes(serverDir) {
163
+ const wsDir = join2(serverDir, "ws");
164
+ if (!existsSync2(wsDir) || !statSync2(wsDir).isDirectory()) {
165
+ return [];
166
+ }
167
+ const results = [];
168
+ walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {
169
+ let rel = relative2(wsDir, absPath);
170
+ rel = rel.replace(/\\/g, "/");
171
+ rel = rel.slice(0, -extname2(rel).length);
172
+ if (rel.endsWith("/index")) rel = rel.slice(0, -6);
173
+ else if (rel === "index") rel = "";
174
+ results.push({
175
+ filePath: absPath,
176
+ wsPath: `/ws/${rel}`
177
+ });
178
+ });
179
+ return results;
180
+ }
181
+
182
+ export {
183
+ compilePattern,
184
+ matchRoute,
185
+ HTTP_METHODS,
186
+ scanServerRoutes,
187
+ scanWebSocketRoutes
188
+ };
189
+ //# sourceMappingURL=chunk-QLGES2HC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/contracts/http-methods.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/match.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\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 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","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/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"]}
@@ -2,10 +2,12 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  compilePattern,
5
- scanServerActions,
6
5
  scanServerRoutes,
7
6
  scanWebSocketRoutes
8
- } from "./chunk-GQSTRXXB.js";
7
+ } from "./chunk-QLGES2HC.js";
8
+ import {
9
+ scanServerActions
10
+ } from "./chunk-MOT42NAH.js";
9
11
 
10
12
  // src/server/scan/manifest.ts
11
13
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
@@ -20,7 +22,8 @@ function generateManifest(serverDir) {
20
22
  routes: routes.map((r) => ({
21
23
  filePath: relative(serverDir, r.filePath),
22
24
  routePath: r.routePath,
23
- paramNames: r.paramNames
25
+ paramNames: r.paramNames,
26
+ ...r.methods !== void 0 ? { methods: r.methods } : {}
24
27
  })),
25
28
  actions: actions.map((a) => ({
26
29
  filePath: relative(serverDir, a.filePath),
@@ -49,7 +52,8 @@ function loadManifest(distDir, serverDir) {
49
52
  filePath: resolve(serverDir, r.filePath),
50
53
  routePath: r.routePath,
51
54
  paramNames,
52
- pattern
55
+ pattern,
56
+ ...r.methods !== void 0 ? { methods: r.methods } : {}
53
57
  };
54
58
  });
55
59
  const actions = raw.actions.map((a) => ({
@@ -68,4 +72,4 @@ export {
68
72
  writeManifest,
69
73
  loadManifest
70
74
  };
71
- //# sourceMappingURL=chunk-JZ5WEG3S.js.map
75
+ //# sourceMappingURL=chunk-SEVYARIU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/manifest.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time manifest emitter / loader. All paths derived from `distDir`\n * + `serverDir`, themselves resolved from `process.cwd()`. No HTTP input.\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { join, resolve, relative } from 'node:path'\n\nimport { scanServerActions } from './action-scan.js'\nimport type { ActionNode } from './action-scan.js'\nimport { compilePattern } from './match.js'\nimport type { ServerRouteNode } from './match.js'\nimport { scanServerRoutes } from './scan.js'\nimport { scanWebSocketRoutes } from './ws-scan.js'\nimport type { WebSocketRouteNode } from './ws-scan.js'\n\n// --- Manifest Types ---\n\nexport interface ManifestRoute {\n filePath: string\n routePath: string\n paramNames: string[]\n /** HTTP methods (uppercase) the route file exports. Optional — manifests\n * generated before G1 omit this; loaders treat absence as \"unknown\". */\n methods?: string[]\n}\n\nexport interface ManifestAction {\n filePath: string\n actionPath: string\n}\n\nexport interface ManifestWebSocket {\n filePath: string\n wsPath: string\n}\n\nexport interface TheoManifest {\n version: 1\n generatedAt: string\n routes: ManifestRoute[]\n actions: ManifestAction[]\n websockets: ManifestWebSocket[]\n}\n\nexport interface LoadedManifest {\n routes: ServerRouteNode[]\n actions: ActionNode[]\n websockets: WebSocketRouteNode[]\n}\n\n// --- Generate ---\n\nexport function generateManifest(serverDir: string): TheoManifest {\n const routes = scanServerRoutes(serverDir)\n const actions = scanServerActions(serverDir)\n const websockets = scanWebSocketRoutes(serverDir)\n\n return {\n version: 1,\n generatedAt: new Date().toISOString(),\n routes: routes.map((r) => ({\n filePath: relative(serverDir, r.filePath),\n routePath: r.routePath,\n paramNames: r.paramNames,\n ...(r.methods !== undefined ? { methods: r.methods } : {}),\n })),\n actions: actions.map((a) => ({\n filePath: relative(serverDir, a.filePath),\n actionPath: a.actionPath,\n })),\n websockets: websockets.map((w) => ({\n filePath: relative(serverDir, w.filePath),\n wsPath: w.wsPath,\n })),\n }\n}\n\n// --- Write ---\n\nexport function writeManifest(manifest: TheoManifest, outputDir: string): void {\n mkdirSync(outputDir, { recursive: true })\n const manifestPath = join(outputDir, 'manifest.json')\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))\n}\n\n// --- Load ---\n\nexport function loadManifest(distDir: string, serverDir: string): LoadedManifest {\n const manifestPath = join(distDir, 'manifest.json')\n\n if (!existsSync(manifestPath)) {\n throw new Error(`No manifest found at ${manifestPath}. Run \"theo build\" first.`)\n }\n\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8')) as TheoManifest\n\n const routes: ServerRouteNode[] = raw.routes.map((r) => {\n const { pattern, paramNames } = compilePattern(r.routePath)\n return {\n filePath: resolve(serverDir, r.filePath),\n routePath: r.routePath,\n paramNames,\n pattern,\n ...(r.methods !== undefined ? { methods: r.methods } : {}),\n }\n })\n\n const actions: ActionNode[] = raw.actions.map((a) => ({\n filePath: resolve(serverDir, a.filePath),\n actionPath: a.actionPath,\n }))\n\n const websockets: WebSocketRouteNode[] = raw.websockets.map((w) => ({\n filePath: resolve(serverDir, w.filePath),\n wsPath: w.wsPath,\n }))\n\n return { routes, actions, websockets }\n}\n"],"mappings":";;;;;;;;;;;;AAIA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,MAAM,SAAS,gBAAgB;AA+CjC,SAAS,iBAAiB,WAAiC;AAChE,QAAM,SAAS,iBAAiB,SAAS;AACzC,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,aAAa,oBAAoB,SAAS;AAEhD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA,MACzB,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,WAAW,EAAE;AAAA,MACb,YAAY,EAAE;AAAA,MACd,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1D,EAAE;AAAA,IACF,SAAS,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,YAAY,EAAE;AAAA,IAChB,EAAE;AAAA,IACF,YAAY,WAAW,IAAI,CAAC,OAAO;AAAA,MACjC,UAAU,SAAS,WAAW,EAAE,QAAQ;AAAA,MACxC,QAAQ,EAAE;AAAA,IACZ,EAAE;AAAA,EACJ;AACF;AAIO,SAAS,cAAc,UAAwB,WAAyB;AAC7E,YAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,QAAM,eAAe,KAAK,WAAW,eAAe;AACpD,gBAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAC/D;AAIO,SAAS,aAAa,SAAiB,WAAmC;AAC/E,QAAM,eAAe,KAAK,SAAS,eAAe;AAElD,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,UAAM,IAAI,MAAM,wBAAwB,YAAY,2BAA2B;AAAA,EACjF;AAEA,QAAM,MAAM,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAE1D,QAAM,SAA4B,IAAI,OAAO,IAAI,CAAC,MAAM;AACtD,UAAM,EAAE,SAAS,WAAW,IAAI,eAAe,EAAE,SAAS;AAC1D,WAAO;AAAA,MACL,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,MACvC,WAAW,EAAE;AAAA,MACb;AAAA,MACA;AAAA,MACA,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AAED,QAAM,UAAwB,IAAI,QAAQ,IAAI,CAAC,OAAO;AAAA,IACpD,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,IACvC,YAAY,EAAE;AAAA,EAChB,EAAE;AAEF,QAAM,aAAmC,IAAI,WAAW,IAAI,CAAC,OAAO;AAAA,IAClE,UAAU,QAAQ,WAAW,EAAE,QAAQ;AAAA,IACvC,QAAQ,EAAE;AAAA,EACZ,EAAE;AAEF,SAAO,EAAE,QAAQ,SAAS,WAAW;AACvC;","names":[]}
@@ -13,16 +13,10 @@ import {
13
13
  resolveTransformer,
14
14
  safeAudit,
15
15
  sendError
16
- } from "./chunk-WQCPKA2L.js";
17
- import {
18
- matchRoute,
19
- scanServerActions,
20
- scanServerRoutes,
21
- scanWebSocketRoutes
22
- } from "./chunk-GQSTRXXB.js";
16
+ } from "./chunk-C27AZN46.js";
23
17
  import {
24
18
  loadConfig
25
- } from "./chunk-S3JBIFKS.js";
19
+ } from "./chunk-MG7H3LAW.js";
26
20
  import {
27
21
  broadcastToDevtools
28
22
  } from "./chunk-5ODOE6EF.js";
@@ -33,6 +27,14 @@ import {
33
27
  import {
34
28
  buildServicesProxyConfig
35
29
  } from "./chunk-WZ7CPVQB.js";
30
+ import {
31
+ matchRoute,
32
+ scanServerRoutes,
33
+ scanWebSocketRoutes
34
+ } from "./chunk-QLGES2HC.js";
35
+ import {
36
+ scanServerActions
37
+ } from "./chunk-MOT42NAH.js";
36
38
 
37
39
  // src/vite-plugin/index.ts
38
40
  import { existsSync as existsSync5 } from "fs";
@@ -468,6 +470,54 @@ function generateRouteManifest(tree) {
468
470
  return lines.join("\n");
469
471
  }
470
472
 
473
+ // src/server/security/csrf-readiness-store.ts
474
+ var CsrfReadinessStore = class _CsrfReadinessStore {
475
+ static MAX_ENTRIES = 1e3;
476
+ entries = /* @__PURE__ */ new Map();
477
+ keyFor(event) {
478
+ return `${event.method}\0${event.path}\0${event.reason}`;
479
+ }
480
+ record(event) {
481
+ const key = this.keyFor(event);
482
+ const now = (/* @__PURE__ */ new Date()).toISOString();
483
+ const existing = this.entries.get(key);
484
+ if (existing) {
485
+ existing.count++;
486
+ existing.lastSeen = now;
487
+ return;
488
+ }
489
+ if (this.entries.size >= _CsrfReadinessStore.MAX_ENTRIES) {
490
+ const firstKey = this.entries.keys().next().value;
491
+ if (firstKey !== void 0) this.entries.delete(firstKey);
492
+ }
493
+ this.entries.set(key, { count: 1, firstSeen: now, lastSeen: now });
494
+ }
495
+ summary() {
496
+ let total = 0;
497
+ const routes = [];
498
+ for (const [key, entry] of this.entries) {
499
+ const [method, path, reason] = key.split("\0");
500
+ routes.push({
501
+ method,
502
+ path,
503
+ reason,
504
+ count: entry.count,
505
+ firstSeen: entry.firstSeen,
506
+ lastSeen: entry.lastSeen
507
+ });
508
+ total += entry.count;
509
+ }
510
+ return {
511
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
512
+ totalEvents: total,
513
+ routes
514
+ };
515
+ }
516
+ reset() {
517
+ this.entries.clear();
518
+ }
519
+ };
520
+
471
521
  // src/vite-plugin/action-middleware.ts
472
522
  import { randomUUID } from "crypto";
473
523
  var PREFIX = "/api/__actions/";
@@ -536,13 +586,17 @@ function createActionMiddleware(vite, serverDir, options) {
536
586
  csrfMode,
537
587
  disallowed
538
588
  );
539
- logRequest({
540
- method: req.method ?? "POST",
541
- url,
542
- status: res.statusCode,
543
- duration: Date.now() - start,
544
- requestId
545
- });
589
+ logRequest(
590
+ {
591
+ method: req.method ?? "POST",
592
+ url,
593
+ status: res.statusCode,
594
+ duration: Date.now() - start,
595
+ requestId
596
+ },
597
+ void 0,
598
+ req
599
+ );
546
600
  })();
547
601
  };
548
602
  }
@@ -974,6 +1028,35 @@ async function handleBatchInline(req, res, batching, requestId) {
974
1028
  sendError(res, "BATCH_ERROR", message, 400, void 0, requestId);
975
1029
  }
976
1030
  }
1031
+ async function runPluginsBeforeRouteMatch(pluginRunner, reqCtx) {
1032
+ const { req, res, requestId, start, url } = reqCtx;
1033
+ try {
1034
+ const hookResult = await pluginRunner.runOnRequest({
1035
+ request: req,
1036
+ response: res,
1037
+ ctx: {},
1038
+ requestId
1039
+ });
1040
+ if (hookResult.shortCircuited) {
1041
+ logRequest(
1042
+ {
1043
+ method: req.method ?? "GET",
1044
+ url,
1045
+ status: res.statusCode,
1046
+ duration: Date.now() - start,
1047
+ requestId
1048
+ },
1049
+ void 0,
1050
+ req
1051
+ );
1052
+ return true;
1053
+ }
1054
+ } catch (err) {
1055
+ sendError(res, "PLUGIN_ERROR", err.message, 500, void 0, requestId);
1056
+ return true;
1057
+ }
1058
+ return false;
1059
+ }
977
1060
  function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
978
1061
  const opts = rateLimitConfigOrOptions && "windowMs" in rateLimitConfigOrOptions ? { rateLimitConfig: rateLimitConfigOrOptions } : rateLimitConfigOrOptions ?? {};
979
1062
  const loadModule = createViteLoader(vite);
@@ -991,7 +1074,7 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
991
1074
  const csrfReadinessStore = opts.csrfReadinessStore;
992
1075
  const servicesProxyPrefixes = opts.servicesProxyPrefixes ?? [];
993
1076
  return (req, res, next) => {
994
- void (async () => {
1077
+ const handle = async () => {
995
1078
  const url = req.url ?? "";
996
1079
  if (await handleCspReportIfMatch(req, res, url, { auditLogger, onCspViolation })) {
997
1080
  return;
@@ -1015,6 +1098,9 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
1015
1098
  if (applyRateLimitOr429(req, res, { rateLimiter, requestId, url, start })) {
1016
1099
  return;
1017
1100
  }
1101
+ if (pluginRunner && await runPluginsBeforeRouteMatch(pluginRunner, { req, res, requestId, start, url })) {
1102
+ return;
1103
+ }
1018
1104
  if (await handleBatchIfMatch(req, res, { url, batching, requestId, start })) {
1019
1105
  return;
1020
1106
  }
@@ -1026,13 +1112,17 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
1026
1112
  const suggestion = findSuggestion(urlPath, routePaths);
1027
1113
  const msg = suggestion ? `API route not found: ${urlPath}. Did you mean: ${suggestion}?` : "API route not found";
1028
1114
  sendError(res, "NOT_FOUND", msg, 404, void 0, requestId);
1029
- logRequest({
1030
- method: req.method ?? "GET",
1031
- url,
1032
- status: 404,
1033
- duration: Date.now() - start,
1034
- requestId
1035
- });
1115
+ logRequest(
1116
+ {
1117
+ method: req.method ?? "GET",
1118
+ url,
1119
+ status: 404,
1120
+ duration: Date.now() - start,
1121
+ requestId
1122
+ },
1123
+ void 0,
1124
+ req
1125
+ );
1036
1126
  return;
1037
1127
  }
1038
1128
  const method = (req.method ?? "GET").toUpperCase();
@@ -1050,8 +1140,13 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
1050
1140
  csrfMode,
1051
1141
  disallowed
1052
1142
  });
1053
- logRequest({ method, url, status: res.statusCode, duration: Date.now() - start, requestId });
1054
- })();
1143
+ logRequest(
1144
+ { method, url, status: res.statusCode, duration: Date.now() - start, requestId },
1145
+ void 0,
1146
+ req
1147
+ );
1148
+ };
1149
+ void handle();
1055
1150
  };
1056
1151
  }
1057
1152
 
@@ -1077,7 +1172,7 @@ function resolveExportSubpath(pkgRoot, subpath) {
1077
1172
  }
1078
1173
  let target;
1079
1174
  if (typeof exp === "string") target = exp;
1080
- else if (exp && typeof exp === "object") {
1175
+ else if (typeof exp === "object") {
1081
1176
  const e = exp;
1082
1177
  target = e.import ?? e.default;
1083
1178
  }
@@ -1147,7 +1242,9 @@ var DEFAULT_RESOLVED = {
1147
1242
  disallowed: void 0,
1148
1243
  cors: void 0,
1149
1244
  auditLogger: void 0,
1150
- devtoolsEnabled: true
1245
+ devtoolsEnabled: true,
1246
+ openapi: void 0,
1247
+ distDir: ".theo"
1151
1248
  };
1152
1249
  async function resolvePluginConfig(projectRoot) {
1153
1250
  try {
@@ -1176,7 +1273,9 @@ async function resolvePluginConfig(projectRoot) {
1176
1273
  disallowed: userConfig.security?.disallowed,
1177
1274
  cors: userConfig.security?.cors,
1178
1275
  auditLogger,
1179
- devtoolsEnabled: userConfig.devtools !== false
1276
+ devtoolsEnabled: userConfig.devtools !== false,
1277
+ openapi: userConfig.openapi,
1278
+ distDir: userConfig.distDir
1180
1279
  };
1181
1280
  } catch {
1182
1281
  return DEFAULT_RESOLVED;
@@ -1513,9 +1612,7 @@ async function integrateUseTheoUI(cwd, opts) {
1513
1612
  }
1514
1613
  const tailwindPlugins = normalizePluginReturn(tailwindPlugin);
1515
1614
  if (tailwindPlugins === null) {
1516
- console.warn(
1517
- `[theokit] @tailwindcss/vite returned unexpected shape. Skipping auto-config.`
1518
- );
1615
+ console.warn(`[theokit] @tailwindcss/vite returned unexpected shape. Skipping auto-config.`);
1519
1616
  return [];
1520
1617
  }
1521
1618
  const uiPlugins = normalizePluginReturn(uiPlugin);
@@ -1789,7 +1886,42 @@ async function theoPluginAsync(rootOrOptions) {
1789
1886
  })
1790
1887
  );
1791
1888
  }
1792
- return [theoPlugin(rootOrOptions), ...uiPlugins, ...servicesPlugins];
1889
+ const { appTypedClientPlugin } = await import("./app-typed-client-4L4L4BWD.js");
1890
+ const appClientPlugin = appTypedClientPlugin({
1891
+ cwd: projectRoot,
1892
+ serverDir: resolve4(projectRoot, "server"),
1893
+ distDir: resolve4(projectRoot, ".theo")
1894
+ });
1895
+ const { actionsVirtualModule } = await import("./actions-virtual-module-E6PU47GV.js");
1896
+ const actionsPlugin = actionsVirtualModule({
1897
+ serverDir: resolve4(projectRoot, "server"),
1898
+ distDir: resolve4(projectRoot, ".theo")
1899
+ });
1900
+ return [
1901
+ theoPlugin(rootOrOptions),
1902
+ ...uiPlugins,
1903
+ ...servicesPlugins,
1904
+ appClientPlugin,
1905
+ actionsPlugin
1906
+ ];
1907
+ }
1908
+ function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
1909
+ const include = [];
1910
+ if (existsSync5(resolve4(projectRoot, "node_modules", "@usetheo", "ui"))) {
1911
+ include.push("@usetheo/ui");
1912
+ }
1913
+ if (existsSync5(resolve4(projectRoot, "node_modules", "lucide-react"))) {
1914
+ include.push("lucide-react");
1915
+ }
1916
+ include.push("devalue");
1917
+ if (Array.isArray(viteOptimizeDeps)) {
1918
+ for (const pkg of viteOptimizeDeps) {
1919
+ if (typeof pkg === "string" && pkg.length > 0 && !include.includes(pkg)) {
1920
+ include.push(pkg);
1921
+ }
1922
+ }
1923
+ }
1924
+ return include;
1793
1925
  }
1794
1926
  function theoPlugin(rootOrOptions) {
1795
1927
  const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions ?? {};
@@ -1808,6 +1940,8 @@ function theoPlugin(rootOrOptions) {
1808
1940
  let cors;
1809
1941
  let auditLogger;
1810
1942
  let devtoolsEnabled = true;
1943
+ let resolvedOpenApi;
1944
+ let resolvedDistDir = ".theo";
1811
1945
  let isDevMode = false;
1812
1946
  let configLoadedOnce = false;
1813
1947
  return {
@@ -1826,6 +1960,8 @@ function theoPlugin(rootOrOptions) {
1826
1960
  cors = resolved.cors;
1827
1961
  auditLogger = resolved.auditLogger;
1828
1962
  devtoolsEnabled = resolved.devtoolsEnabled;
1963
+ resolvedOpenApi = resolved.openapi;
1964
+ resolvedDistDir = resolved.distDir;
1829
1965
  },
1830
1966
  // Vite calls this hook (sync return is fine — no awaits left after
1831
1967
  // the auto-chain moved to theoPluginAsync). The @usetheo/ui +
@@ -1833,13 +1969,7 @@ function theoPlugin(rootOrOptions) {
1833
1969
  // Vite drops plugins returned from a child plugin's config() hook.
1834
1970
  config() {
1835
1971
  const ext = existsSync5(resolve4(theoSrcDir, "index.ts")) ? ".ts" : ".js";
1836
- const optimizeDepsInclude = [];
1837
- if (existsSync5(resolve4(projectRoot, "node_modules", "@usetheo", "ui"))) {
1838
- optimizeDepsInclude.push("@usetheo/ui");
1839
- }
1840
- if (existsSync5(resolve4(projectRoot, "node_modules", "lucide-react"))) {
1841
- optimizeDepsInclude.push("lucide-react");
1842
- }
1972
+ const optimizeDepsInclude = buildOptimizeDepsInclude(projectRoot, options.viteOptimizeDeps);
1843
1973
  const warmupClientFiles = [];
1844
1974
  for (const candidate of ["app/layout.tsx", "app/page.tsx", "app/page.jsx"]) {
1845
1975
  const full = resolve4(projectRoot, candidate);
@@ -1848,7 +1978,10 @@ function theoPlugin(rootOrOptions) {
1848
1978
  const servicesProxy = options.services && Object.keys(options.services).length > 0 ? buildServicesProxyConfig(options.services) : void 0;
1849
1979
  return {
1850
1980
  envPrefix: "THEO_PUBLIC_",
1851
- optimizeDeps: optimizeDepsInclude.length > 0 ? { include: optimizeDepsInclude } : void 0,
1981
+ optimizeDeps: {
1982
+ ...optimizeDepsInclude.length > 0 ? { include: optimizeDepsInclude } : {},
1983
+ force: true
1984
+ },
1852
1985
  server: {
1853
1986
  ...warmupClientFiles.length > 0 ? { warmup: { clientFiles: warmupClientFiles } } : {},
1854
1987
  ...servicesProxy !== void 0 ? { proxy: servicesProxy } : {},
@@ -1963,7 +2096,7 @@ function theoPlugin(rootOrOptions) {
1963
2096
  ].join("\n");
1964
2097
  }
1965
2098
  },
1966
- configureServer(server) {
2099
+ async configureServer(server) {
1967
2100
  isDevMode = true;
1968
2101
  globalThis.__theoViteHotServer = server;
1969
2102
  server.ws.on("theo:devtools:request-manifest", () => {
@@ -1978,6 +2111,7 @@ function theoPlugin(rootOrOptions) {
1978
2111
  createActionMiddleware(server, serverDir, { pluginRunner, csrfMode, disallowed })
1979
2112
  );
1980
2113
  const servicesProxyPrefixes = options.services && Object.keys(options.services).length > 0 ? Object.values(options.services).map((s) => s.proxy) : [];
2114
+ const csrfReadinessStore = options.csrfReadinessStore ?? new CsrfReadinessStore();
1981
2115
  server.middlewares.use(
1982
2116
  createApiMiddleware(server, serverDir, {
1983
2117
  rateLimitConfig: options.rateLimit,
@@ -1989,7 +2123,8 @@ function theoPlugin(rootOrOptions) {
1989
2123
  disallowed,
1990
2124
  cors,
1991
2125
  auditLogger,
1992
- servicesProxyPrefixes
2126
+ servicesProxyPrefixes,
2127
+ csrfReadinessStore
1993
2128
  })
1994
2129
  );
1995
2130
  const configPath = resolve4(projectRoot, "theo.config.ts");
@@ -2018,6 +2153,29 @@ function theoPlugin(rootOrOptions) {
2018
2153
  }
2019
2154
  server.watcher.on("add", handleRouteChange);
2020
2155
  server.watcher.on("unlink", handleRouteChange);
2156
+ if (resolvedOpenApi !== void 0) {
2157
+ const openApiCfg = resolvedOpenApi;
2158
+ const { reEmitOpenApi } = await import("./dev-emit-I3CKOHML.js");
2159
+ const openApiServerDir = resolve4(projectRoot, "server");
2160
+ const openApiDistDir = resolve4(projectRoot, resolvedDistDir);
2161
+ const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
2162
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2163
+ server.watcher.on("change", (file) => {
2164
+ if (isRouteFileForOpenApi(file)) {
2165
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2166
+ }
2167
+ });
2168
+ server.watcher.on("add", (file) => {
2169
+ if (isRouteFileForOpenApi(file)) {
2170
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2171
+ }
2172
+ });
2173
+ server.watcher.on("unlink", (file) => {
2174
+ if (isRouteFileForOpenApi(file)) {
2175
+ void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
2176
+ }
2177
+ });
2178
+ }
2021
2179
  setupWsUpgrade(server, projectRoot);
2022
2180
  server.httpServer?.once("close", () => {
2023
2181
  ;
@@ -2035,4 +2193,4 @@ export {
2035
2193
  theoPluginAsync,
2036
2194
  theoPlugin
2037
2195
  };
2038
- //# sourceMappingURL=chunk-ZK5JCE5J.js.map
2196
+ //# sourceMappingURL=chunk-TFECHXR5.js.map