theokit 0.1.0-alpha.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 (48) hide show
  1. package/dist/build-NSKFOAFX.js +49 -0
  2. package/dist/build-NSKFOAFX.js.map +1 -0
  3. package/dist/chunk-ASGEGAWL.js +480 -0
  4. package/dist/chunk-ASGEGAWL.js.map +1 -0
  5. package/dist/chunk-ATSTRYYT.js +894 -0
  6. package/dist/chunk-ATSTRYYT.js.map +1 -0
  7. package/dist/chunk-KPU44T6G.js +71 -0
  8. package/dist/chunk-KPU44T6G.js.map +1 -0
  9. package/dist/chunk-MMZZBPMX.js +335 -0
  10. package/dist/chunk-MMZZBPMX.js.map +1 -0
  11. package/dist/chunk-N5YH2UDG.js +85 -0
  12. package/dist/chunk-N5YH2UDG.js.map +1 -0
  13. package/dist/chunk-SAVVU5LG.js +66 -0
  14. package/dist/chunk-SAVVU5LG.js.map +1 -0
  15. package/dist/chunk-TXMUCDJT.js +43 -0
  16. package/dist/chunk-TXMUCDJT.js.map +1 -0
  17. package/dist/chunk-U3OJFWK3.js +162 -0
  18. package/dist/chunk-U3OJFWK3.js.map +1 -0
  19. package/dist/cli/index.js +79 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/client/index.d.ts +37 -0
  22. package/dist/client/index.js +57 -0
  23. package/dist/client/index.js.map +1 -0
  24. package/dist/cloudflare-CVGN7FOU.js +88 -0
  25. package/dist/cloudflare-CVGN7FOU.js.map +1 -0
  26. package/dist/dev-7UJK3M2O.js +47 -0
  27. package/dist/dev-7UJK3M2O.js.map +1 -0
  28. package/dist/docker-M253W54T.js +101 -0
  29. package/dist/docker-M253W54T.js.map +1 -0
  30. package/dist/generate-AA7ZE42F.js +116 -0
  31. package/dist/generate-AA7ZE42F.js.map +1 -0
  32. package/dist/index.d.ts +88 -0
  33. package/dist/index.js +171 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/rate-limit-C6hHXIj1.d.ts +13 -0
  36. package/dist/routes-YP357VAC.js +60 -0
  37. package/dist/routes-YP357VAC.js.map +1 -0
  38. package/dist/server/index.d.ts +94 -0
  39. package/dist/server/index.js +148 -0
  40. package/dist/server/index.js.map +1 -0
  41. package/dist/start-BIS3RCFQ.js +243 -0
  42. package/dist/start-BIS3RCFQ.js.map +1 -0
  43. package/dist/vercel-747SR2FB.js +80 -0
  44. package/dist/vercel-747SR2FB.js.map +1 -0
  45. package/dist/vite-plugin/index.d.ts +12 -0
  46. package/dist/vite-plugin/index.js +8 -0
  47. package/dist/vite-plugin/index.js.map +1 -0
  48. package/package.json +49 -0
@@ -0,0 +1,894 @@
1
+ import {
2
+ AuthRequiredError,
3
+ createRateLimiter
4
+ } from "./chunk-SAVVU5LG.js";
5
+
6
+ // src/vite-plugin/index.ts
7
+ import { resolve as resolve5, basename as basename2, dirname } from "path";
8
+ import { existsSync as existsSync6, readFileSync } from "fs";
9
+ import { fileURLToPath } from "url";
10
+
11
+ // src/router/scan.ts
12
+ import { readdirSync, statSync, existsSync } from "fs";
13
+ import { join, resolve } from "path";
14
+
15
+ // src/router/types.ts
16
+ var ROUTE_FILE_NAMES = [
17
+ "page",
18
+ "layout",
19
+ "error",
20
+ "loading",
21
+ "not-found"
22
+ ];
23
+ var ROUTE_FILE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
24
+ var ROUTE_FILE_REGEX = /^(page|layout|error|loading|not-found)\.(tsx|ts|jsx|js)$/;
25
+ function isRouteFile(filename) {
26
+ return ROUTE_FILE_REGEX.test(filename);
27
+ }
28
+
29
+ // src/router/scan.ts
30
+ function toNodeKey(name) {
31
+ if (name === "not-found") return "notFound";
32
+ return name;
33
+ }
34
+ function setRouteFile(node, key, value) {
35
+ switch (key) {
36
+ case "page":
37
+ node.page = value;
38
+ break;
39
+ case "layout":
40
+ node.layout = value;
41
+ break;
42
+ case "error":
43
+ node.error = value;
44
+ break;
45
+ case "loading":
46
+ node.loading = value;
47
+ break;
48
+ case "notFound":
49
+ node.notFound = value;
50
+ break;
51
+ }
52
+ }
53
+ function scanDir(dir, segment, routePath) {
54
+ const node = { segment, path: routePath, children: [] };
55
+ const entries = readdirSync(dir, { withFileTypes: true });
56
+ for (const name of ROUTE_FILE_NAMES) {
57
+ const key = toNodeKey(name);
58
+ if (node[key] !== void 0) continue;
59
+ for (const ext of ROUTE_FILE_EXTENSIONS) {
60
+ const filename = `${name}${ext}`;
61
+ if (existsSync(join(dir, filename))) {
62
+ setRouteFile(node, key, resolve(dir, filename));
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ for (const entry of entries) {
68
+ if (!entry.isDirectory()) continue;
69
+ if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
70
+ const childPath = routePath === "/" ? `/${entry.name}` : `${routePath}/${entry.name}`;
71
+ const child = scanDir(join(dir, entry.name), entry.name, childPath);
72
+ const hasRouteFile = child.page || child.layout || child.error || child.loading || child.notFound;
73
+ if (hasRouteFile || child.children.length > 0) {
74
+ node.children.push(child);
75
+ }
76
+ }
77
+ return node;
78
+ }
79
+ function scanRoutes(appDir) {
80
+ if (!existsSync(appDir)) {
81
+ throw new Error(`App directory does not exist: ${appDir}`);
82
+ }
83
+ if (!statSync(appDir).isDirectory()) {
84
+ throw new Error(`App path is not a directory: ${appDir}`);
85
+ }
86
+ return scanDir(appDir, "", "/");
87
+ }
88
+
89
+ // src/router/generate.ts
90
+ function normalizePath(p) {
91
+ return p.replace(/\\/g, "/");
92
+ }
93
+ function safeVarName(segment, prefix) {
94
+ const safe = segment.replace(/[^a-zA-Z0-9]/g, "_") || "root";
95
+ return `${prefix}_${safe}`;
96
+ }
97
+ function generateRouteManifest(tree) {
98
+ const imports = [];
99
+ let hasLayout = false;
100
+ function collectImports(node) {
101
+ const seg = node.segment || "root";
102
+ if (node.page) {
103
+ imports.push({
104
+ varName: safeVarName(seg, "Page"),
105
+ importPath: normalizePath(node.page)
106
+ });
107
+ }
108
+ if (node.layout) {
109
+ hasLayout = true;
110
+ imports.push({
111
+ varName: safeVarName(seg, "Layout"),
112
+ importPath: normalizePath(node.layout)
113
+ });
114
+ }
115
+ if (node.error) {
116
+ imports.push({
117
+ varName: safeVarName(seg, "Error"),
118
+ importPath: normalizePath(node.error)
119
+ });
120
+ }
121
+ if (node.loading) {
122
+ imports.push({
123
+ varName: safeVarName(seg, "Loading"),
124
+ importPath: normalizePath(node.loading)
125
+ });
126
+ }
127
+ if (node.notFound) {
128
+ imports.push({
129
+ varName: safeVarName(seg, "NotFound"),
130
+ importPath: normalizePath(node.notFound)
131
+ });
132
+ }
133
+ for (const child of node.children) {
134
+ collectImports(child);
135
+ }
136
+ }
137
+ collectImports(tree);
138
+ const lines = [
139
+ `import React, { Suspense, lazy } from 'react'`
140
+ ];
141
+ if (hasLayout) {
142
+ lines.push(`import { Outlet } from 'react-router'`);
143
+ }
144
+ lines.push("");
145
+ for (const imp of imports) {
146
+ lines.push(
147
+ `const ${imp.varName} = lazy(() => import('${imp.importPath}'))`
148
+ );
149
+ }
150
+ lines.push("");
151
+ function genRouteConfig(node, isRoot) {
152
+ const seg = node.segment || "root";
153
+ const childConfigs = [];
154
+ if (node.page) {
155
+ const pageVar = safeVarName(seg, "Page");
156
+ let pageElement = `React.createElement(${pageVar})`;
157
+ if (node.loading) {
158
+ const loadVar = safeVarName(seg, "Loading");
159
+ pageElement = `React.createElement(Suspense, { fallback: React.createElement(${loadVar}) }, ${pageElement})`;
160
+ }
161
+ childConfigs.push(`{ index: true, element: ${pageElement} }`);
162
+ }
163
+ for (const child of node.children) {
164
+ childConfigs.push(genRouteConfig(child, false));
165
+ }
166
+ if (node.notFound) {
167
+ const nfVar = safeVarName(seg, "NotFound");
168
+ childConfigs.push(
169
+ `{ path: '*', element: React.createElement(${nfVar}) }`
170
+ );
171
+ }
172
+ let childrenArray = `[${childConfigs.join(", ")}]`;
173
+ if (node.error) {
174
+ const errVar = safeVarName(seg, "Error");
175
+ childrenArray = `[{ errorElement: React.createElement(${errVar}), children: ${childrenArray} }]`;
176
+ }
177
+ if (node.layout) {
178
+ const layoutVar = safeVarName(seg, "Layout");
179
+ const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
180
+ return `{ ${pathPart}, element: React.createElement(${layoutVar}), children: ${childrenArray} }`;
181
+ }
182
+ if (isRoot) {
183
+ if (childConfigs.length === 0 && !node.page) {
184
+ return `{ path: '/', children: [] }`;
185
+ }
186
+ return `{ path: '/', children: ${childrenArray} }`;
187
+ }
188
+ if (node.page && node.children.length === 0 && !node.error && !node.notFound) {
189
+ const pageVar = safeVarName(seg, "Page");
190
+ let pageElement = `React.createElement(${pageVar})`;
191
+ if (node.loading) {
192
+ const loadVar = safeVarName(seg, "Loading");
193
+ pageElement = `React.createElement(Suspense, { fallback: React.createElement(${loadVar}) }, ${pageElement})`;
194
+ }
195
+ return `{ path: '${node.segment}', element: ${pageElement} }`;
196
+ }
197
+ return `{ path: '${node.segment}', children: ${childrenArray} }`;
198
+ }
199
+ const routeConfig = genRouteConfig(tree, true);
200
+ lines.push(`export const routes = [${routeConfig}]`);
201
+ return lines.join("\n");
202
+ }
203
+
204
+ // src/router/entry.ts
205
+ function generateEntryClient(ssr) {
206
+ const rootMethod = ssr ? "hydrateRoot" : "createRoot";
207
+ const renderCall = ssr ? ` ${rootMethod}(el,
208
+ React.createElement(Suspense, { fallback: null },
209
+ React.createElement(RouterProvider, { router })
210
+ )
211
+ )` : ` ${rootMethod}(el).render(
212
+ React.createElement(Suspense, { fallback: null },
213
+ React.createElement(RouterProvider, { router })
214
+ )
215
+ )`;
216
+ return [
217
+ `import React, { Suspense } from 'react'`,
218
+ `import { ${rootMethod} } from 'react-dom/client'`,
219
+ `import { createBrowserRouter, RouterProvider } from 'react-router'`,
220
+ `import { routes } from '/@theo/route-manifest'`,
221
+ ``,
222
+ `const router = createBrowserRouter(routes)`,
223
+ `const el = document.getElementById('root')`,
224
+ `if (el) {`,
225
+ renderCall,
226
+ `}`
227
+ ].join("\n");
228
+ }
229
+
230
+ // src/router/entry-server.ts
231
+ function generateEntryServer() {
232
+ return [
233
+ `import React from 'react'`,
234
+ `import { renderToPipeableStream } from 'react-dom/server'`,
235
+ `import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
236
+ `import { PassThrough } from 'node:stream'`,
237
+ `import { routes } from '/@theo/route-manifest'`,
238
+ ``,
239
+ `export async function render(url) {`,
240
+ ` const handler = createStaticHandler(routes)`,
241
+ ` const request = new Request('http://localhost' + url)`,
242
+ ` const context = await handler.query(request)`,
243
+ ``,
244
+ ` if (context instanceof Response) {`,
245
+ ` return { redirect: context }`,
246
+ ` }`,
247
+ ``,
248
+ ` const router = createStaticRouter(handler.dataRoutes, context)`,
249
+ ` const app = React.createElement(StaticRouterProvider, { router, context })`,
250
+ ``,
251
+ ` return new Promise((resolve, reject) => {`,
252
+ ` let html = ''`,
253
+ ` const passthrough = new PassThrough()`,
254
+ ` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
255
+ ` passthrough.on('end', () => { resolve(html) })`,
256
+ ` passthrough.on('error', reject)`,
257
+ ``,
258
+ ` const { pipe } = renderToPipeableStream(app, {`,
259
+ ` onAllReady() { pipe(passthrough) },`,
260
+ ` onShellError(err) { reject(err) },`,
261
+ ` onError(err) { console.error('[SSR Error]', err) },`,
262
+ ` })`,
263
+ ` })`,
264
+ `}`
265
+ ].join("\n");
266
+ }
267
+
268
+ // src/vite-plugin/api-middleware.ts
269
+ import { randomUUID } from "crypto";
270
+
271
+ // src/server/scan.ts
272
+ import { readdirSync as readdirSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
273
+ import { join as join2, resolve as resolve2, relative, extname } from "path";
274
+
275
+ // src/server/match.ts
276
+ function compilePattern(routePath) {
277
+ const paramNames = [];
278
+ const regexStr = routePath.replace(/:([^/]+)/g, (_, name) => {
279
+ paramNames.push(name);
280
+ return "([^/]+)";
281
+ });
282
+ return { pattern: new RegExp(`^${regexStr}$`), paramNames };
283
+ }
284
+ function matchRoute(url, routes) {
285
+ let path = url.split("?")[0];
286
+ if (path.length > 1 && path.endsWith("/")) {
287
+ path = path.slice(0, -1);
288
+ }
289
+ for (const route of routes) {
290
+ const match = route.pattern.exec(path);
291
+ if (match) {
292
+ const params = {};
293
+ route.paramNames.forEach((name, i) => {
294
+ params[name] = match[i + 1];
295
+ });
296
+ return { route, params };
297
+ }
298
+ }
299
+ return null;
300
+ }
301
+
302
+ // src/server/scan.ts
303
+ var ROUTE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
304
+ function fileToRoutePath(filePath, routesDir) {
305
+ let rel = relative(routesDir, filePath);
306
+ const ext = extname(rel);
307
+ rel = rel.slice(0, -ext.length);
308
+ rel = rel.replace(/\\/g, "/");
309
+ if (rel.endsWith("/index")) {
310
+ rel = rel.slice(0, -6);
311
+ } else if (rel === "index") {
312
+ rel = "";
313
+ }
314
+ rel = rel.replace(/\[([^\]]+)\]/g, ":$1");
315
+ return `/api/${rel}`;
316
+ }
317
+ function scanDir2(dir, routesDir, results) {
318
+ const entries = readdirSync2(dir, { withFileTypes: true });
319
+ for (const entry of entries) {
320
+ const fullPath = join2(dir, entry.name);
321
+ if (entry.isDirectory()) {
322
+ if (!entry.name.startsWith("_") && !entry.name.startsWith(".")) {
323
+ scanDir2(fullPath, routesDir, results);
324
+ }
325
+ } else if (entry.isFile()) {
326
+ const ext = extname(entry.name);
327
+ if (!ROUTE_EXTENSIONS.includes(ext)) continue;
328
+ const routePath = fileToRoutePath(fullPath, routesDir);
329
+ const { pattern, paramNames } = compilePattern(routePath);
330
+ results.push({
331
+ filePath: resolve2(fullPath),
332
+ routePath,
333
+ paramNames,
334
+ pattern
335
+ });
336
+ }
337
+ }
338
+ }
339
+ function scanServerRoutes(serverDir) {
340
+ const routesDir = join2(serverDir, "routes");
341
+ if (!existsSync2(routesDir) || !statSync2(routesDir).isDirectory()) {
342
+ return [];
343
+ }
344
+ const results = [];
345
+ scanDir2(routesDir, routesDir, results);
346
+ results.sort((a, b) => {
347
+ if (a.paramNames.length === 0 && b.paramNames.length > 0) return -1;
348
+ if (a.paramNames.length > 0 && b.paramNames.length === 0) return 1;
349
+ return a.routePath.localeCompare(b.routePath);
350
+ });
351
+ return results;
352
+ }
353
+
354
+ // src/server/middleware-runner.ts
355
+ import { existsSync as existsSync3 } from "fs";
356
+ import { join as join3 } from "path";
357
+ async function runMiddlewareAndContext(req, res, loadModule, serverDir) {
358
+ const middlewarePath = join3(serverDir, "middleware.ts");
359
+ if (existsSync3(middlewarePath)) {
360
+ const mod = await loadModule(middlewarePath);
361
+ const mw = mod.default;
362
+ if (typeof mw === "function") {
363
+ let nextCalled = false;
364
+ await mw(req, res, async () => {
365
+ nextCalled = true;
366
+ });
367
+ if (!nextCalled || res.writableEnded) {
368
+ return { ctx: {}, aborted: true };
369
+ }
370
+ }
371
+ }
372
+ let ctx = {};
373
+ const contextPath = join3(serverDir, "context.ts");
374
+ if (existsSync3(contextPath)) {
375
+ const mod = await loadModule(contextPath);
376
+ if (typeof mod.createContext === "function") {
377
+ ctx = await mod.createContext({ request: req, response: res });
378
+ }
379
+ }
380
+ return { ctx, aborted: false };
381
+ }
382
+
383
+ // src/server/execute.ts
384
+ var METHODS_WITH_BODY = ["POST", "PUT", "PATCH"];
385
+ function sendJson(res, data, status = 200) {
386
+ const body = JSON.stringify(data);
387
+ res.writeHead(status, {
388
+ "Content-Type": "application/json",
389
+ "Content-Length": Buffer.byteLength(body)
390
+ });
391
+ res.end(body);
392
+ }
393
+ function sendError(res, code, message, status, issues, requestId) {
394
+ const errorMessage = code === "INTERNAL_ERROR" && process.env.NODE_ENV === "production" ? "Internal server error" : message;
395
+ if (code === "INTERNAL_ERROR") {
396
+ console.error(`[${requestId ?? "no-id"}] ${message}`);
397
+ }
398
+ sendJson(
399
+ res,
400
+ {
401
+ error: {
402
+ code,
403
+ message: errorMessage,
404
+ ...requestId ? { requestId } : {},
405
+ ...issues ? { issues } : {}
406
+ }
407
+ },
408
+ status
409
+ );
410
+ }
411
+ function parseBody(req) {
412
+ return new Promise((resolve6, reject) => {
413
+ const method = req.method?.toUpperCase() ?? "GET";
414
+ if (!METHODS_WITH_BODY.includes(method)) {
415
+ return resolve6(void 0);
416
+ }
417
+ const contentType = req.headers["content-type"] ?? "";
418
+ const chunks = [];
419
+ req.on("data", (chunk) => chunks.push(chunk));
420
+ req.on("end", () => {
421
+ const raw = Buffer.concat(chunks).toString();
422
+ if (!raw) return resolve6(void 0);
423
+ if (!contentType.includes("application/json")) {
424
+ return reject(new Error("Expected Content-Type: application/json"));
425
+ }
426
+ try {
427
+ resolve6(JSON.parse(raw));
428
+ } catch {
429
+ reject(new Error("Invalid JSON body"));
430
+ }
431
+ });
432
+ req.on("error", reject);
433
+ });
434
+ }
435
+ async function executeRoute(route, method, params, req, res, loadModule, serverDir, requestId) {
436
+ try {
437
+ let ctx = {};
438
+ if (serverDir) {
439
+ const result = await runMiddlewareAndContext(req, res, loadModule, serverDir);
440
+ if (result.aborted) return;
441
+ ctx = result.ctx;
442
+ }
443
+ const mod = await loadModule(route.filePath);
444
+ const routeConfig = mod[method];
445
+ if (!routeConfig) {
446
+ sendError(res, "METHOD_NOT_ALLOWED", `Method ${method} not allowed`, 405, void 0, requestId);
447
+ return;
448
+ }
449
+ const handler = typeof routeConfig === "function" ? routeConfig : routeConfig.handler;
450
+ if (typeof handler !== "function") {
451
+ sendError(res, "INTERNAL_ERROR", "Route handler is not a function", 500, void 0, requestId);
452
+ return;
453
+ }
454
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
455
+ const query = Object.fromEntries(url.searchParams);
456
+ let body;
457
+ try {
458
+ body = await parseBody(req);
459
+ } catch (err) {
460
+ sendError(res, "VALIDATION_ERROR", err.message, 400, void 0, requestId);
461
+ return;
462
+ }
463
+ const rc = routeConfig;
464
+ if (rc.query && typeof rc.query.safeParse === "function") {
465
+ const result = rc.query.safeParse(query);
466
+ if (!result.success) {
467
+ sendError(res, "VALIDATION_ERROR", "Invalid query parameters", 400, result.error.issues, requestId);
468
+ return;
469
+ }
470
+ Object.assign(query, result.data);
471
+ }
472
+ if (rc.body && typeof rc.body.safeParse === "function") {
473
+ const result = rc.body.safeParse(body);
474
+ if (!result.success) {
475
+ sendError(res, "VALIDATION_ERROR", "Invalid request body", 400, result.error.issues, requestId);
476
+ return;
477
+ }
478
+ body = result.data;
479
+ }
480
+ if (rc.params && typeof rc.params.safeParse === "function") {
481
+ const result = rc.params.safeParse(params);
482
+ if (!result.success) {
483
+ sendError(res, "VALIDATION_ERROR", "Invalid route parameters", 400, result.error.issues, requestId);
484
+ return;
485
+ }
486
+ Object.assign(params, result.data);
487
+ }
488
+ const handlerResult = await handler({ query, body, params, request: req, ctx });
489
+ if (handlerResult === void 0 || handlerResult === null) {
490
+ sendJson(res, null, rc.status ?? 204);
491
+ return;
492
+ }
493
+ if (handlerResult instanceof Response) {
494
+ res.writeHead(handlerResult.status, Object.fromEntries(handlerResult.headers));
495
+ if (handlerResult.body) {
496
+ const reader = handlerResult.body.getReader();
497
+ try {
498
+ while (true) {
499
+ const { done, value } = await reader.read();
500
+ if (done) break;
501
+ res.write(value);
502
+ }
503
+ } catch {
504
+ }
505
+ }
506
+ res.end();
507
+ return;
508
+ }
509
+ sendJson(res, handlerResult, rc.status ?? 200);
510
+ } catch (err) {
511
+ if (err instanceof AuthRequiredError) {
512
+ sendError(res, err.code, err.message, err.status, void 0, requestId);
513
+ return;
514
+ }
515
+ sendError(res, "INTERNAL_ERROR", err.message ?? "Internal server error", 500, void 0, requestId);
516
+ }
517
+ }
518
+
519
+ // src/server/module-loader.ts
520
+ import { pathToFileURL } from "url";
521
+ function createViteLoader(vite) {
522
+ return (path) => vite.ssrLoadModule(path);
523
+ }
524
+
525
+ // src/server/logger.ts
526
+ var defaultLogger = (log) => {
527
+ console.log(JSON.stringify(log));
528
+ };
529
+ function logRequest(info, customLogger) {
530
+ const log = {
531
+ level: "info",
532
+ ...info,
533
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
534
+ };
535
+ const logger = customLogger ?? defaultLogger;
536
+ logger(log);
537
+ }
538
+
539
+ // src/vite-plugin/api-middleware.ts
540
+ function createApiMiddleware(vite, serverDir, rateLimitConfig) {
541
+ const loadModule = createViteLoader(vite);
542
+ const rateLimiter = rateLimitConfig ? createRateLimiter(rateLimitConfig) : null;
543
+ return async (req, res, next) => {
544
+ const url = req.url ?? "";
545
+ if (!url.startsWith("/api/")) {
546
+ return next();
547
+ }
548
+ const requestId = randomUUID();
549
+ const start = Date.now();
550
+ res.setHeader("x-request-id", requestId);
551
+ if (rateLimiter) {
552
+ const check = rateLimiter(req);
553
+ for (const [k, v] of Object.entries(check.headers)) res.setHeader(k, v);
554
+ if (check.limited) {
555
+ sendError(res, "RATE_LIMITED", "Too many requests", 429, void 0, requestId);
556
+ logRequest({ method: req.method ?? "GET", url, status: 429, duration: Date.now() - start, requestId });
557
+ return;
558
+ }
559
+ }
560
+ const routes = scanServerRoutes(serverDir);
561
+ const match = matchRoute(url, routes);
562
+ if (!match) {
563
+ sendError(res, "NOT_FOUND", "API route not found", 404, void 0, requestId);
564
+ logRequest({ method: req.method ?? "GET", url, status: 404, duration: Date.now() - start, requestId });
565
+ return;
566
+ }
567
+ const method = (req.method ?? "GET").toUpperCase();
568
+ await executeRoute(match.route, method, match.params, req, res, loadModule, serverDir, requestId);
569
+ logRequest({ method, url, status: res.statusCode, duration: Date.now() - start, requestId });
570
+ };
571
+ }
572
+
573
+ // src/vite-plugin/action-middleware.ts
574
+ import { randomUUID as randomUUID2 } from "crypto";
575
+
576
+ // src/server/action-scan.ts
577
+ import { readdirSync as readdirSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
578
+ import { join as join4, resolve as resolve3, relative as relative2, extname as extname2 } from "path";
579
+ var ACTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
580
+ function scanDir3(dir, actionsDir, results) {
581
+ const entries = readdirSync3(dir, { withFileTypes: true });
582
+ for (const entry of entries) {
583
+ const fullPath = join4(dir, entry.name);
584
+ if (entry.isDirectory()) {
585
+ if (!entry.name.startsWith("_") && !entry.name.startsWith(".")) {
586
+ scanDir3(fullPath, actionsDir, results);
587
+ }
588
+ } else if (entry.isFile()) {
589
+ const ext = extname2(entry.name);
590
+ if (!ACTION_EXTENSIONS.includes(ext)) continue;
591
+ let rel = relative2(actionsDir, fullPath);
592
+ rel = rel.replace(/\\/g, "/");
593
+ rel = rel.slice(0, -ext.length);
594
+ results.push({
595
+ filePath: resolve3(fullPath),
596
+ actionPath: rel
597
+ });
598
+ }
599
+ }
600
+ }
601
+ function scanServerActions(serverDir) {
602
+ const actionsDir = join4(serverDir, "actions");
603
+ if (!existsSync4(actionsDir) || !statSync3(actionsDir).isDirectory()) {
604
+ return [];
605
+ }
606
+ const results = [];
607
+ scanDir3(actionsDir, actionsDir, results);
608
+ return results;
609
+ }
610
+
611
+ // src/server/csrf.ts
612
+ function validateCsrf(req) {
613
+ if (req.headers["x-theo-action"] !== "1") {
614
+ return { valid: false, reason: "Missing X-Theo-Action header" };
615
+ }
616
+ const origin = req.headers["origin"];
617
+ if (!origin) {
618
+ return { valid: true };
619
+ }
620
+ const host = req.headers["host"];
621
+ if (!host) {
622
+ return { valid: true };
623
+ }
624
+ try {
625
+ const originHost = new URL(origin).host;
626
+ if (originHost !== host) {
627
+ return { valid: false, reason: `Origin ${origin} does not match host ${host}` };
628
+ }
629
+ } catch {
630
+ return { valid: false, reason: `Invalid origin: ${origin}` };
631
+ }
632
+ return { valid: true };
633
+ }
634
+
635
+ // src/server/action-execute.ts
636
+ async function executeAction(filePath, exportName, req, res, loadModule, serverDir, requestId) {
637
+ try {
638
+ const method = (req.method ?? "GET").toUpperCase();
639
+ if (method !== "POST") {
640
+ sendError(res, "METHOD_NOT_ALLOWED", "Actions only accept POST", 405, void 0, requestId);
641
+ return;
642
+ }
643
+ const csrf = validateCsrf(req);
644
+ if (!csrf.valid) {
645
+ sendError(res, "FORBIDDEN", csrf.reason, 403, void 0, requestId);
646
+ return;
647
+ }
648
+ let ctx = {};
649
+ if (serverDir) {
650
+ const result2 = await runMiddlewareAndContext(req, res, loadModule, serverDir);
651
+ if (result2.aborted) return;
652
+ ctx = result2.ctx;
653
+ }
654
+ const mod = await loadModule(filePath);
655
+ const actionConfig = mod[exportName];
656
+ if (!actionConfig || typeof actionConfig.handler !== "function" || !actionConfig.input) {
657
+ sendError(res, "NOT_FOUND", `Action "${exportName}" not found`, 404, void 0, requestId);
658
+ return;
659
+ }
660
+ let body;
661
+ try {
662
+ body = await parseBody(req);
663
+ } catch (err) {
664
+ sendError(res, "VALIDATION_ERROR", err.message, 400, void 0, requestId);
665
+ return;
666
+ }
667
+ const input = actionConfig.input;
668
+ const result = input.safeParse(body);
669
+ if (!result.success) {
670
+ sendError(res, "VALIDATION_ERROR", "Invalid action input", 400, result.error?.issues, requestId);
671
+ return;
672
+ }
673
+ const handlerResult = await actionConfig.handler({ input: result.data, ctx });
674
+ if (handlerResult === void 0 || handlerResult === null) {
675
+ sendJson(res, null, 204);
676
+ return;
677
+ }
678
+ sendJson(res, handlerResult, 200);
679
+ } catch (err) {
680
+ if (err instanceof AuthRequiredError) {
681
+ sendError(res, err.code, err.message, err.status, void 0, requestId);
682
+ return;
683
+ }
684
+ sendError(res, "INTERNAL_ERROR", err.message ?? "Internal server error", 500, void 0, requestId);
685
+ }
686
+ }
687
+
688
+ // src/vite-plugin/action-middleware.ts
689
+ var PREFIX = "/api/__actions/";
690
+ function createActionMiddleware(vite, serverDir) {
691
+ const loadModule = createViteLoader(vite);
692
+ return async (req, res, next) => {
693
+ const url = req.url ?? "";
694
+ if (!url.startsWith(PREFIX)) {
695
+ return next();
696
+ }
697
+ const requestId = randomUUID2();
698
+ const start = Date.now();
699
+ res.setHeader("x-request-id", requestId);
700
+ const pathAfterPrefix = url.slice(PREFIX.length).split("?")[0];
701
+ const segments = pathAfterPrefix.split("/").filter(Boolean);
702
+ if (segments.length < 2) {
703
+ sendError(res, "BAD_REQUEST", "Action URL must be /api/__actions/{file}/{exportName}", 400, void 0, requestId);
704
+ logRequest({ method: req.method ?? "POST", url, status: 400, duration: Date.now() - start, requestId });
705
+ return;
706
+ }
707
+ const exportName = segments[segments.length - 1];
708
+ const actionPath = segments.slice(0, -1).join("/");
709
+ const actions = scanServerActions(serverDir);
710
+ const action = actions.find((a) => a.actionPath === actionPath);
711
+ if (!action) {
712
+ sendError(res, "NOT_FOUND", `Action file "${actionPath}" not found`, 404, void 0, requestId);
713
+ logRequest({ method: req.method ?? "POST", url, status: 404, duration: Date.now() - start, requestId });
714
+ return;
715
+ }
716
+ await executeAction(action.filePath, exportName, req, res, loadModule, serverDir, requestId);
717
+ logRequest({ method: req.method ?? "POST", url, status: res.statusCode, duration: Date.now() - start, requestId });
718
+ };
719
+ }
720
+
721
+ // src/server/ws-scan.ts
722
+ import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync4 } from "fs";
723
+ import { join as join5, resolve as resolve4, relative as relative3, extname as extname3 } from "path";
724
+ var WS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
725
+ function scanDir4(dir, wsDir, results) {
726
+ const entries = readdirSync4(dir, { withFileTypes: true });
727
+ for (const entry of entries) {
728
+ const fullPath = join5(dir, entry.name);
729
+ if (entry.isDirectory()) {
730
+ if (!entry.name.startsWith("_") && !entry.name.startsWith(".")) {
731
+ scanDir4(fullPath, wsDir, results);
732
+ }
733
+ } else if (entry.isFile()) {
734
+ const ext = extname3(entry.name);
735
+ if (!WS_EXTENSIONS.includes(ext)) continue;
736
+ let rel = relative3(wsDir, fullPath);
737
+ rel = rel.replace(/\\/g, "/");
738
+ rel = rel.slice(0, -ext.length);
739
+ if (rel.endsWith("/index")) rel = rel.slice(0, -6);
740
+ else if (rel === "index") rel = "";
741
+ results.push({
742
+ filePath: resolve4(fullPath),
743
+ wsPath: `/ws/${rel}`
744
+ });
745
+ }
746
+ }
747
+ }
748
+ function scanWebSocketRoutes(serverDir) {
749
+ const wsDir = join5(serverDir, "ws");
750
+ if (!existsSync5(wsDir) || !statSync4(wsDir).isDirectory()) {
751
+ return [];
752
+ }
753
+ const results = [];
754
+ scanDir4(wsDir, wsDir, results);
755
+ return results;
756
+ }
757
+
758
+ // src/vite-plugin/index.ts
759
+ var VIRTUAL_ENTRY_ID = "/@theo/entry-client";
760
+ var RESOLVED_ENTRY_ID = "\0@theo/entry-client";
761
+ var VIRTUAL_MANIFEST_ID = "/@theo/route-manifest";
762
+ var RESOLVED_MANIFEST_ID = "\0@theo/route-manifest";
763
+ var VIRTUAL_ENTRY_SERVER_ID = "/@theo/entry-server";
764
+ var RESOLVED_ENTRY_SERVER_ID = "\0@theo/entry-server";
765
+ function theoPlugin(rootOrOptions) {
766
+ const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions ?? {};
767
+ const projectRoot = options.root ?? process.cwd();
768
+ const appDir = resolve5(projectRoot, "app");
769
+ const ssrEnabled = options.ssr ?? false;
770
+ const currentDir = dirname(fileURLToPath(import.meta.url));
771
+ const theoSrcDir = resolve5(currentDir, "..");
772
+ return {
773
+ name: "theo",
774
+ config() {
775
+ const ext = existsSync6(resolve5(theoSrcDir, "index.ts")) ? ".ts" : ".js";
776
+ return {
777
+ envPrefix: "THEO_PUBLIC_",
778
+ resolve: {
779
+ alias: [
780
+ { find: "theokit/server", replacement: resolve5(theoSrcDir, `server/index${ext}`) },
781
+ { find: "theokit", replacement: resolve5(theoSrcDir, `index${ext}`) }
782
+ ]
783
+ }
784
+ };
785
+ },
786
+ resolveId(id) {
787
+ if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;
788
+ if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;
789
+ if (id === VIRTUAL_ENTRY_SERVER_ID) return RESOLVED_ENTRY_SERVER_ID;
790
+ },
791
+ load(id) {
792
+ if (id === RESOLVED_ENTRY_ID) {
793
+ return generateEntryClient(ssrEnabled);
794
+ }
795
+ if (id === RESOLVED_MANIFEST_ID) {
796
+ const tree = scanRoutes(appDir);
797
+ return generateRouteManifest(tree);
798
+ }
799
+ if (id === RESOLVED_ENTRY_SERVER_ID) {
800
+ return generateEntryServer();
801
+ }
802
+ },
803
+ configureServer(server) {
804
+ const serverDir = resolve5(projectRoot, "server");
805
+ server.middlewares.use(createActionMiddleware(server, serverDir));
806
+ server.middlewares.use(createApiMiddleware(server, serverDir, options.rateLimit));
807
+ if (ssrEnabled) {
808
+ server.middlewares.use(async (req, res, next) => {
809
+ const url = req.url ?? "/";
810
+ if (url.startsWith("/api/") || url.startsWith("/@") || url.startsWith("/node_modules/") || url.includes(".")) {
811
+ return next();
812
+ }
813
+ try {
814
+ const indexPath = resolve5(projectRoot, "index.html");
815
+ let template = readFileSync(indexPath, "utf-8");
816
+ template = await server.transformIndexHtml(url, template);
817
+ const mod = await server.ssrLoadModule(VIRTUAL_ENTRY_SERVER_ID);
818
+ const result = await mod.render(url);
819
+ if (result && typeof result === "object" && "redirect" in result) {
820
+ res.writeHead(302, { Location: result.redirect.headers.get("location") ?? "/" });
821
+ res.end();
822
+ return;
823
+ }
824
+ const ssrHtml = result;
825
+ const rootDivMatch = template.match(/<div id=["']root["'][^>]*>/);
826
+ if (!rootDivMatch) {
827
+ res.writeHead(200, { "Content-Type": "text/html" });
828
+ res.end(template);
829
+ return;
830
+ }
831
+ const splitIdx = template.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
832
+ const html = template.slice(0, splitIdx) + ssrHtml + template.slice(splitIdx);
833
+ res.writeHead(200, { "Content-Type": "text/html" });
834
+ res.end(html);
835
+ } catch (err) {
836
+ server.ssrFixStacktrace(err);
837
+ console.error("[SSR Dev Error]", err);
838
+ return next();
839
+ }
840
+ });
841
+ }
842
+ function handleRouteChange(filePath) {
843
+ if (!isRouteFile(basename2(filePath))) return;
844
+ if (!filePath.startsWith(appDir)) return;
845
+ const mod = server.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);
846
+ if (mod) {
847
+ server.moduleGraph.invalidateModule(mod);
848
+ server.ws.send({ type: "full-reload" });
849
+ }
850
+ }
851
+ server.watcher.on("add", handleRouteChange);
852
+ server.watcher.on("unlink", handleRouteChange);
853
+ const wsRoutes = scanWebSocketRoutes(resolve5(projectRoot, "server"));
854
+ if (wsRoutes.length > 0 && server.httpServer) {
855
+ import("ws").then(({ WebSocketServer }) => {
856
+ const wss = new WebSocketServer({ noServer: true });
857
+ server.httpServer.on("upgrade", async (request, socket, head) => {
858
+ const url = request.url ?? "/";
859
+ if (!url.startsWith("/ws/")) return;
860
+ const wsPath = url.split("?")[0];
861
+ const match = wsRoutes.find((r) => r.wsPath === wsPath);
862
+ if (!match) {
863
+ socket.destroy();
864
+ return;
865
+ }
866
+ try {
867
+ const mod = await server.ssrLoadModule(match.filePath);
868
+ const handler = mod.default ?? mod;
869
+ wss.handleUpgrade(request, socket, head, (ws) => {
870
+ handler.onOpen?.(ws, request);
871
+ ws.on("message", (data) => handler.onMessage?.(ws, data.toString()));
872
+ ws.on("close", (code, reason) => handler.onClose?.(ws, code, reason));
873
+ ws.on("error", (err) => handler.onError?.(ws, err));
874
+ });
875
+ } catch {
876
+ socket.destroy();
877
+ }
878
+ });
879
+ }).catch(() => {
880
+ console.warn('[Theo] WebSocket routes found but "ws" package not installed. Run: npm install ws');
881
+ });
882
+ }
883
+ }
884
+ };
885
+ }
886
+
887
+ export {
888
+ isRouteFile,
889
+ scanRoutes,
890
+ generateRouteManifest,
891
+ generateEntryClient,
892
+ theoPlugin
893
+ };
894
+ //# sourceMappingURL=chunk-ATSTRYYT.js.map