tova 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tova.js +438 -58
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +172 -32
- package/src/analyzer/client-analyzer.js +21 -5
- package/src/analyzer/scope.js +78 -3
- package/src/codegen/base-codegen.js +754 -45
- package/src/codegen/client-codegen.js +293 -36
- package/src/codegen/codegen.js +10 -15
- package/src/codegen/server-codegen.js +189 -40
- package/src/codegen/wasm-codegen.js +610 -0
- package/src/lexer/lexer.js +157 -109
- package/src/lexer/tokens.js +3 -0
- package/src/lsp/server.js +148 -12
- package/src/parser/ast.js +2 -1
- package/src/parser/client-parser.js +10 -3
- package/src/parser/parser.js +144 -150
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +307 -59
- package/src/runtime/ssr.js +101 -34
- package/src/stdlib/inline.js +333 -24
- package/src/stdlib/native-bridge.js +150 -0
- package/src/version.js +1 -1
|
@@ -293,9 +293,19 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
293
293
|
// Check if rate limiting is needed
|
|
294
294
|
const needsRateLimitStore = !!rateLimitConfig || routes.some(r => (r.decorators || []).some(d => d.name === 'rate_limit'));
|
|
295
295
|
|
|
296
|
+
// Fast mode: emit a minimal request handler when no complex features are used
|
|
297
|
+
const allRoutesStatic = routes.every(r => !r.path.includes(':') && !r.path.includes('*'));
|
|
298
|
+
const hasDynamicRoutes = !allRoutesStatic;
|
|
299
|
+
const isFastMode = !corsConfig && !authConfig && !sessionConfig && !rateLimitConfig &&
|
|
300
|
+
!errorHandler && !wsDecl && !staticDecl && !compressionConfig &&
|
|
301
|
+
middlewares.length === 0 && !dbConfig && subscriptions.length === 0 &&
|
|
302
|
+
backgroundJobs.length === 0 && sseDecls.length === 0 && !cacheConfig &&
|
|
303
|
+
routes.every(r => !(r.decorators || []).length && !r._groupMiddlewares?.length && !r._version);
|
|
304
|
+
|
|
296
305
|
// ════════════════════════════════════════════════════════════
|
|
297
306
|
// 1. Distributed Tracing
|
|
298
307
|
// ════════════════════════════════════════════════════════════
|
|
308
|
+
if (!isFastMode) {
|
|
299
309
|
lines.push('// ── Distributed Tracing ──');
|
|
300
310
|
lines.push('import { AsyncLocalStorage } from "node:async_hooks";');
|
|
301
311
|
lines.push('const __requestContext = new AsyncLocalStorage();');
|
|
@@ -308,12 +318,22 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
308
318
|
lines.push(' return store ? store.locals : {};');
|
|
309
319
|
lines.push('}');
|
|
310
320
|
lines.push('');
|
|
321
|
+
}
|
|
311
322
|
|
|
312
323
|
// ════════════════════════════════════════════════════════════
|
|
313
324
|
// 2. Env Validation (F6) — fail fast
|
|
314
325
|
// ════════════════════════════════════════════════════════════
|
|
315
326
|
if (envDecls.length > 0) {
|
|
316
327
|
lines.push('// ── Env Validation ──');
|
|
328
|
+
// Collect all required env vars without defaults and validate presence before any exit
|
|
329
|
+
const requiredEnvs = envDecls.filter(d => !d.defaultValue);
|
|
330
|
+
if (requiredEnvs.length > 0) {
|
|
331
|
+
lines.push(`const __envErrors = [];`);
|
|
332
|
+
for (const decl of requiredEnvs) {
|
|
333
|
+
lines.push(`if (process.env.${decl.name} === undefined || process.env.${decl.name} === "") __envErrors.push("${decl.name}");`);
|
|
334
|
+
}
|
|
335
|
+
lines.push(`if (__envErrors.length > 0) { console.error("Missing required env vars: " + __envErrors.join(", ")); process.exit(1); }`);
|
|
336
|
+
}
|
|
317
337
|
for (const decl of envDecls) {
|
|
318
338
|
const envName = decl.name;
|
|
319
339
|
const ta = decl.typeAnnotation;
|
|
@@ -323,8 +343,6 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
323
343
|
if (decl.defaultValue) {
|
|
324
344
|
const defaultExpr = this.genExpression(decl.defaultValue);
|
|
325
345
|
lines.push(` if (__raw === undefined || __raw === "") return ${defaultExpr};`);
|
|
326
|
-
} else {
|
|
327
|
-
lines.push(` if (__raw === undefined || __raw === "") { console.error("Required env var ${envName} is not set"); process.exit(1); }`);
|
|
328
346
|
}
|
|
329
347
|
switch (typeName) {
|
|
330
348
|
case 'Int':
|
|
@@ -797,12 +815,18 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
797
815
|
// ════════════════════════════════════════════════════════════
|
|
798
816
|
lines.push('// ── Router ──');
|
|
799
817
|
lines.push('const __routes = [];');
|
|
818
|
+
lines.push('const __staticRoutes = new Map();'); // Fast lookup for static routes
|
|
800
819
|
lines.push('function __addRoute(method, path, handler, version) {');
|
|
820
|
+
lines.push(' const isStatic = !path.includes(":") && !path.includes("*");');
|
|
821
|
+
lines.push(' if (isStatic) {');
|
|
822
|
+
lines.push(' const key = method + " " + path;');
|
|
823
|
+
lines.push(' __staticRoutes.set(key, { method, handler, _path: path, _version: version || null });');
|
|
824
|
+
lines.push(' }');
|
|
801
825
|
lines.push(' let pattern = path');
|
|
802
826
|
lines.push(' .replace(/\\*([a-zA-Z_][a-zA-Z0-9_]*)/g, "(?<$1>.+)")');
|
|
803
827
|
lines.push(' .replace(/\\*$/g, "(.*)")');
|
|
804
828
|
lines.push(' .replace(/:([^/]+)/g, "(?<$1>[^/]+)");');
|
|
805
|
-
lines.push(' __routes.push({ method, regex: new RegExp(`^${pattern}$`), handler, _path: path, _version: version || null });');
|
|
829
|
+
lines.push(' __routes.push({ method, regex: new RegExp(`^${pattern}$`), handler, _path: path, _version: version || null, _isStatic: isStatic });');
|
|
806
830
|
lines.push('}');
|
|
807
831
|
lines.push('');
|
|
808
832
|
|
|
@@ -830,13 +854,12 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
830
854
|
lines.push('}');
|
|
831
855
|
} else {
|
|
832
856
|
lines.push('// ── CORS ──');
|
|
833
|
-
lines.push('
|
|
834
|
-
lines.push('
|
|
835
|
-
lines.push('
|
|
836
|
-
lines.push('
|
|
837
|
-
lines.push('
|
|
838
|
-
lines.push('
|
|
839
|
-
lines.push('}');
|
|
857
|
+
lines.push('const __corsHeadersConst = Object.freeze({');
|
|
858
|
+
lines.push(' "Access-Control-Allow-Origin": "*",');
|
|
859
|
+
lines.push(' "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",');
|
|
860
|
+
lines.push(' "Access-Control-Allow-Headers": "Content-Type, Authorization",');
|
|
861
|
+
lines.push('});');
|
|
862
|
+
lines.push('function __getCorsHeaders() { return __corsHeadersConst; }');
|
|
840
863
|
}
|
|
841
864
|
lines.push('');
|
|
842
865
|
|
|
@@ -879,12 +902,9 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
879
902
|
lines.push(' const __sig = await crypto.subtle.sign("HMAC", __authKey, new TextEncoder().encode(__sigData));');
|
|
880
903
|
lines.push(' const __expectedSig = btoa(String.fromCharCode(...new Uint8Array(__sig)))');
|
|
881
904
|
lines.push(' .replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/, "");');
|
|
882
|
-
lines.push(' const __sigBuf =
|
|
883
|
-
lines.push(' const __tokBuf =
|
|
884
|
-
lines.push(' if (__sigBuf.length !== __tokBuf.length) return null;');
|
|
885
|
-
lines.push(' let __mismatch = 0;');
|
|
886
|
-
lines.push(' for (let i = 0; i < __sigBuf.length; i++) __mismatch |= __sigBuf[i] ^ __tokBuf[i];');
|
|
887
|
-
lines.push(' if (__mismatch !== 0) return null;');
|
|
905
|
+
lines.push(' const __sigBuf = Buffer.from(__expectedSig);');
|
|
906
|
+
lines.push(' const __tokBuf = Buffer.from(parts[2]);');
|
|
907
|
+
lines.push(' if (__sigBuf.length !== __tokBuf.length || !require("crypto").timingSafeEqual(__sigBuf, __tokBuf)) return null;');
|
|
888
908
|
lines.push(' const __payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));');
|
|
889
909
|
lines.push(' if (__payload.exp && __payload.exp < Math.floor(Date.now() / 1000)) return null;');
|
|
890
910
|
lines.push(' return __payload;');
|
|
@@ -935,9 +955,8 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
935
955
|
lines.push('setInterval(() => {');
|
|
936
956
|
lines.push(' const now = Date.now();');
|
|
937
957
|
lines.push(' for (const [key, entry] of __rateLimitStore) {');
|
|
938
|
-
lines.push('
|
|
939
|
-
lines.push('
|
|
940
|
-
lines.push(' }');
|
|
958
|
+
lines.push(' entry.timestamps = entry.timestamps.filter(t => now - t < 120000);');
|
|
959
|
+
lines.push(' if (entry.timestamps.length === 0) __rateLimitStore.delete(key);');
|
|
941
960
|
lines.push(' }');
|
|
942
961
|
lines.push('}, 60000);');
|
|
943
962
|
lines.push('');
|
|
@@ -2180,6 +2199,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2180
2199
|
// ════════════════════════════════════════════════════════════
|
|
2181
2200
|
// 18. Logging, Static files, WebSocket
|
|
2182
2201
|
// ════════════════════════════════════════════════════════════
|
|
2202
|
+
if (!isFastMode) {
|
|
2183
2203
|
lines.push('// ── Structured Logging ──');
|
|
2184
2204
|
lines.push('let __reqCounter = 0;');
|
|
2185
2205
|
lines.push('function __genRequestId() {');
|
|
@@ -2199,6 +2219,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2199
2219
|
lines.push(' if (__logFile) __logFile.write(entry + "\\n");');
|
|
2200
2220
|
lines.push('}');
|
|
2201
2221
|
lines.push('');
|
|
2222
|
+
}
|
|
2202
2223
|
|
|
2203
2224
|
if (staticDecl) {
|
|
2204
2225
|
lines.push('// ── Static File Serving ──');
|
|
@@ -2348,14 +2369,77 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2348
2369
|
lines.push('');
|
|
2349
2370
|
}
|
|
2350
2371
|
|
|
2372
|
+
if (!isFastMode) {
|
|
2351
2373
|
lines.push('// ── Graceful Drain ──');
|
|
2352
2374
|
lines.push('let __activeRequests = 0;');
|
|
2353
2375
|
lines.push('let __shuttingDown = false;');
|
|
2354
2376
|
lines.push('');
|
|
2377
|
+
}
|
|
2355
2378
|
|
|
2356
2379
|
// ════════════════════════════════════════════════════════════
|
|
2357
2380
|
// 21. Request Handler — with global rate limit check (F2)
|
|
2358
2381
|
// ════════════════════════════════════════════════════════════
|
|
2382
|
+
if (isFastMode) {
|
|
2383
|
+
// Fast mode: emit direct handler references for static routes
|
|
2384
|
+
if (allRoutesStatic && routes.length <= 16) {
|
|
2385
|
+
for (let ri = 0; ri < routes.length; ri++) {
|
|
2386
|
+
const method = routes[ri].method.toUpperCase();
|
|
2387
|
+
const path = routes[ri].path;
|
|
2388
|
+
lines.push(`const __fh${ri} = __staticRoutes.get(${JSON.stringify(method + ' ' + path)}).handler;`);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
// Fast mode: minimal handler with no AsyncLocalStorage, no logging, no request IDs
|
|
2392
|
+
lines.push('// ── Request Handler (fast mode) ──');
|
|
2393
|
+
lines.push('function __handleRequest(req) {');
|
|
2394
|
+
lines.push(' const __rawUrl = req.url;');
|
|
2395
|
+
lines.push(' const __pStart = __rawUrl.indexOf("/", 12);');
|
|
2396
|
+
lines.push(' const __qIdx = __rawUrl.indexOf("?", __pStart);');
|
|
2397
|
+
lines.push(' const __pathname = __qIdx === -1 ? __rawUrl.slice(__pStart) : __rawUrl.slice(__pStart, __qIdx);');
|
|
2398
|
+
lines.push(' const __method = req.method;');
|
|
2399
|
+
|
|
2400
|
+
// OPTIONS fast path
|
|
2401
|
+
lines.push(' if (__method === "OPTIONS") return new Response(null, { status: 204, headers: __corsHeadersConst });');
|
|
2402
|
+
|
|
2403
|
+
// Static route dispatch — emit direct if/else chain using named handler refs
|
|
2404
|
+
if (allRoutesStatic && routes.length <= 16) {
|
|
2405
|
+
for (let ri = 0; ri < routes.length; ri++) {
|
|
2406
|
+
const method = routes[ri].method.toUpperCase();
|
|
2407
|
+
const path = routes[ri].path;
|
|
2408
|
+
const handlerVar = `__fh${ri}`;
|
|
2409
|
+
lines.push(` if (__method === ${JSON.stringify(method)} && __pathname === ${JSON.stringify(path)}) return ${handlerVar}(req, {});`);
|
|
2410
|
+
}
|
|
2411
|
+
// HEAD fallback for GET routes
|
|
2412
|
+
for (let ri = 0; ri < routes.length; ri++) {
|
|
2413
|
+
if (routes[ri].method.toUpperCase() === 'GET') {
|
|
2414
|
+
const handlerVar = `__fh${ri}`;
|
|
2415
|
+
lines.push(` if (__method === "HEAD" && __pathname === ${JSON.stringify(routes[ri].path)}) return ${handlerVar}(req, {});`);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
} else {
|
|
2419
|
+
// Fallback to Map lookup for larger route sets or dynamic routes
|
|
2420
|
+
lines.push(' const __staticKey = __method + " " + __pathname;');
|
|
2421
|
+
lines.push(' const __sr = __staticRoutes.get(__staticKey);');
|
|
2422
|
+
lines.push(' if (__sr) return __sr.handler(req, {});');
|
|
2423
|
+
if (hasDynamicRoutes) {
|
|
2424
|
+
lines.push(' for (const route of __routes) {');
|
|
2425
|
+
lines.push(' if (route._isStatic) continue;');
|
|
2426
|
+
lines.push(' if (__method === route.method || (route.method === "GET" && __method === "HEAD")) {');
|
|
2427
|
+
lines.push(' const match = __pathname.match(route.regex);');
|
|
2428
|
+
lines.push(' if (match) return route.handler(req, match.groups || {});');
|
|
2429
|
+
lines.push(' }');
|
|
2430
|
+
lines.push(' }');
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Client HTML fallback
|
|
2435
|
+
lines.push(' if (__pathname === "/" && typeof __clientHTML !== "undefined") {');
|
|
2436
|
+
lines.push(' return new Response(__clientHTML, { status: 200, headers: { "Content-Type": "text/html" } });');
|
|
2437
|
+
lines.push(' }');
|
|
2438
|
+
lines.push(' return new Response("Not Found", { status: 404 });');
|
|
2439
|
+
lines.push('}');
|
|
2440
|
+
lines.push('');
|
|
2441
|
+
} else {
|
|
2442
|
+
// Full mode: original handler with all features
|
|
2359
2443
|
lines.push('// ── Request Handler ──');
|
|
2360
2444
|
lines.push('async function __handleRequest(req) {');
|
|
2361
2445
|
|
|
@@ -2364,7 +2448,10 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2364
2448
|
lines.push(' }');
|
|
2365
2449
|
lines.push(' __activeRequests++;');
|
|
2366
2450
|
|
|
2367
|
-
lines.push(' const
|
|
2451
|
+
lines.push(' const __rawUrl = req.url;');
|
|
2452
|
+
lines.push(' const __pStart = __rawUrl.indexOf("/", 12);'); // skip "http://x:p" or "https://x:p"
|
|
2453
|
+
lines.push(' const __qIdx = __rawUrl.indexOf("?", __pStart);');
|
|
2454
|
+
lines.push(' const __pathname = __qIdx === -1 ? __rawUrl.slice(__pStart) : __rawUrl.slice(__pStart, __qIdx);');
|
|
2368
2455
|
lines.push(' const __rid = req.headers.get("X-Request-Id") || __genRequestId();');
|
|
2369
2456
|
lines.push(' const __startTime = Date.now();');
|
|
2370
2457
|
lines.push(' const __cors = __getCorsHeaders(req);');
|
|
@@ -2437,16 +2524,77 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2437
2524
|
|
|
2438
2525
|
// Static file serving
|
|
2439
2526
|
if (staticDecl) {
|
|
2440
|
-
lines.push(` if (
|
|
2441
|
-
lines.push(' const __staticRes = await __serveStatic(
|
|
2527
|
+
lines.push(` if (__pathname.startsWith(__staticPrefix)) {`);
|
|
2528
|
+
lines.push(' const __staticRes = await __serveStatic(__pathname, req);');
|
|
2442
2529
|
lines.push(' if (__staticRes) return __staticRes;');
|
|
2443
2530
|
lines.push(' }');
|
|
2444
2531
|
}
|
|
2445
2532
|
|
|
2446
|
-
// Route matching
|
|
2533
|
+
// Route matching — fast path for static routes (no params/wildcards)
|
|
2534
|
+
lines.push(' const __staticKey = req.method + " " + __pathname;');
|
|
2535
|
+
lines.push(' const __staticRoute = __staticRoutes.get(__staticKey) || (req.method === "HEAD" && __staticRoutes.get("GET " + __pathname));');
|
|
2536
|
+
lines.push(' if (__staticRoute) {');
|
|
2537
|
+
lines.push(' const route = __staticRoute;');
|
|
2538
|
+
lines.push(' const match = { groups: {} };');
|
|
2539
|
+
|
|
2540
|
+
// Emit static route handler (same structure as dynamic)
|
|
2541
|
+
if (globalMiddlewares.length > 0) {
|
|
2542
|
+
lines.push(' const __handler = async (__req) => route.handler(__req, {});');
|
|
2543
|
+
lines.push(' const __chain = __middlewares.reduceRight(');
|
|
2544
|
+
lines.push(' (next, mw) => async (__req) => mw(__req, next),');
|
|
2545
|
+
lines.push(' __handler');
|
|
2546
|
+
lines.push(' );');
|
|
2547
|
+
lines.push(' try {');
|
|
2548
|
+
lines.push(' const res = await __chain(req);');
|
|
2549
|
+
lines.push(' __log("info", `${req.method} ${__pathname}`, { rid: __rid, status: res.status, ms: Date.now() - __startTime });');
|
|
2550
|
+
lines.push(' const headers = new Headers(res.headers);');
|
|
2551
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) headers.set(k, v);');
|
|
2552
|
+
lines.push(' return new Response(res.body, { status: res.status, headers });');
|
|
2553
|
+
lines.push(' } catch (err) {');
|
|
2554
|
+
lines.push(' if (err.message === "__BODY_TOO_LARGE__") return Response.json({ error: "Payload Too Large" }, { status: 413, headers: __cors });');
|
|
2555
|
+
if (errorHandler) {
|
|
2556
|
+
lines.push(' try {');
|
|
2557
|
+
lines.push(' const errRes = await __errorHandler(err, req);');
|
|
2558
|
+
lines.push(' if (errRes instanceof Response) {');
|
|
2559
|
+
lines.push(' const headers = new Headers(errRes.headers);');
|
|
2560
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) headers.set(k, v);');
|
|
2561
|
+
lines.push(' return new Response(errRes.body, { status: errRes.status, headers });');
|
|
2562
|
+
lines.push(' }');
|
|
2563
|
+
lines.push(' return Response.json(errRes, { status: 500, headers: __cors });');
|
|
2564
|
+
lines.push(' } catch { /**/ }');
|
|
2565
|
+
}
|
|
2566
|
+
lines.push(' __log("error", `Unhandled error: ${err.message}`, { error: err.stack || err.message });');
|
|
2567
|
+
lines.push(' return Response.json({ error: "Internal Server Error" }, { status: 500, headers: __cors });');
|
|
2568
|
+
lines.push(' }');
|
|
2569
|
+
} else {
|
|
2570
|
+
lines.push(' try {');
|
|
2571
|
+
lines.push(' const res = await route.handler(req, {});');
|
|
2572
|
+
lines.push(' __log("info", `${req.method} ${__pathname}`, { rid: __rid, status: res.status, ms: Date.now() - __startTime });');
|
|
2573
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) res.headers.set(k, v);');
|
|
2574
|
+
lines.push(' return res;');
|
|
2575
|
+
lines.push(' } catch (err) {');
|
|
2576
|
+
lines.push(' if (err.message === "__BODY_TOO_LARGE__") return Response.json({ error: "Payload Too Large" }, { status: 413, headers: __cors });');
|
|
2577
|
+
if (errorHandler) {
|
|
2578
|
+
lines.push(' try {');
|
|
2579
|
+
lines.push(' const errRes = await __errorHandler(err, req);');
|
|
2580
|
+
lines.push(' if (errRes instanceof Response) {');
|
|
2581
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) errRes.headers.set(k, v);');
|
|
2582
|
+
lines.push(' return errRes;');
|
|
2583
|
+
lines.push(' }');
|
|
2584
|
+
lines.push(' return Response.json(errRes, { status: 500, headers: __cors });');
|
|
2585
|
+
lines.push(' } catch { /**/ }');
|
|
2586
|
+
}
|
|
2587
|
+
lines.push(' __log("error", `Unhandled error: ${err.message}`, { error: err.stack || err.message });');
|
|
2588
|
+
lines.push(' return Response.json({ error: "Internal Server Error" }, { status: 500, headers: __cors });');
|
|
2589
|
+
lines.push(' }');
|
|
2590
|
+
}
|
|
2591
|
+
lines.push(' }');
|
|
2592
|
+
|
|
2593
|
+
// Fallback: regex-based matching for dynamic routes
|
|
2447
2594
|
lines.push(' for (const route of __routes) {');
|
|
2595
|
+
lines.push(' if (route._isStatic) continue;'); // Skip static routes already handled
|
|
2448
2596
|
lines.push(' if (req.method === route.method || (route.method === "GET" && req.method === "HEAD" && !__routes.some(r => r.method === "HEAD" && r.regex.source === route.regex.source))) {');
|
|
2449
|
-
lines.push(' const match =
|
|
2597
|
+
lines.push(' const match = __pathname.match(route.regex);');
|
|
2450
2598
|
lines.push(' if (match) {');
|
|
2451
2599
|
|
|
2452
2600
|
if (globalMiddlewares.length > 0) {
|
|
@@ -2457,19 +2605,17 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2457
2605
|
lines.push(' );');
|
|
2458
2606
|
lines.push(' try {');
|
|
2459
2607
|
lines.push(' const res = await __chain(req);');
|
|
2460
|
-
lines.push(' __log("info", `${req.method} ${
|
|
2461
|
-
lines.push(' const
|
|
2462
|
-
lines.push('
|
|
2463
|
-
lines.push(' return new Response(res.body, { status: res.status, headers });');
|
|
2608
|
+
lines.push(' __log("info", `${req.method} ${__pathname}`, { rid: __rid, status: res.status, ms: Date.now() - __startTime });');
|
|
2609
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) res.headers.set(k, v);');
|
|
2610
|
+
lines.push(' return res;');
|
|
2464
2611
|
lines.push(' } catch (err) {');
|
|
2465
2612
|
lines.push(' if (err.message === "__BODY_TOO_LARGE__") return Response.json({ error: "Payload Too Large" }, { status: 413, headers: __cors });');
|
|
2466
2613
|
if (errorHandler) {
|
|
2467
2614
|
lines.push(' try {');
|
|
2468
2615
|
lines.push(' const errRes = await __errorHandler(err, req);');
|
|
2469
2616
|
lines.push(' if (errRes instanceof Response) {');
|
|
2470
|
-
lines.push(' const
|
|
2471
|
-
lines.push('
|
|
2472
|
-
lines.push(' return new Response(errRes.body, { status: errRes.status, headers });');
|
|
2617
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) errRes.headers.set(k, v);');
|
|
2618
|
+
lines.push(' return errRes;');
|
|
2473
2619
|
lines.push(' }');
|
|
2474
2620
|
lines.push(' return Response.json(errRes, { status: 500, headers: __cors });');
|
|
2475
2621
|
lines.push(' } catch { /**/ }');
|
|
@@ -2480,19 +2626,17 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2480
2626
|
} else {
|
|
2481
2627
|
lines.push(' try {');
|
|
2482
2628
|
lines.push(' const res = await route.handler(req, match.groups || {});');
|
|
2483
|
-
lines.push(' __log("info", `${req.method} ${
|
|
2484
|
-
lines.push(' const
|
|
2485
|
-
lines.push('
|
|
2486
|
-
lines.push(' return new Response(res.body, { status: res.status, headers });');
|
|
2629
|
+
lines.push(' __log("info", `${req.method} ${__pathname}`, { rid: __rid, status: res.status, ms: Date.now() - __startTime });');
|
|
2630
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) res.headers.set(k, v);');
|
|
2631
|
+
lines.push(' return res;');
|
|
2487
2632
|
lines.push(' } catch (err) {');
|
|
2488
2633
|
lines.push(' if (err.message === "__BODY_TOO_LARGE__") return Response.json({ error: "Payload Too Large" }, { status: 413, headers: __cors });');
|
|
2489
2634
|
if (errorHandler) {
|
|
2490
2635
|
lines.push(' try {');
|
|
2491
2636
|
lines.push(' const errRes = await __errorHandler(err, req);');
|
|
2492
2637
|
lines.push(' if (errRes instanceof Response) {');
|
|
2493
|
-
lines.push(' const
|
|
2494
|
-
lines.push('
|
|
2495
|
-
lines.push(' return new Response(errRes.body, { status: errRes.status, headers });');
|
|
2638
|
+
lines.push(' for (const [k, v] of Object.entries(__cors)) errRes.headers.set(k, v);');
|
|
2639
|
+
lines.push(' return errRes;');
|
|
2496
2640
|
lines.push(' }');
|
|
2497
2641
|
lines.push(' return Response.json(errRes, { status: 500, headers: __cors });');
|
|
2498
2642
|
lines.push(' } catch { /**/ }');
|
|
@@ -2507,11 +2651,11 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2507
2651
|
lines.push(' }');
|
|
2508
2652
|
|
|
2509
2653
|
// Serve client HTML at root
|
|
2510
|
-
lines.push(' if (
|
|
2654
|
+
lines.push(' if (__pathname === "/" && typeof __clientHTML !== "undefined") {');
|
|
2511
2655
|
lines.push(' return new Response(__clientHTML, { status: 200, headers: { "Content-Type": "text/html", ...(__cors) } });');
|
|
2512
2656
|
lines.push(' }');
|
|
2513
2657
|
lines.push(' const __notFound = Response.json({ error: "Not Found" }, { status: 404, headers: __cors });');
|
|
2514
|
-
lines.push(' __log("warn", "Not Found", { rid: __rid, method: req.method, path:
|
|
2658
|
+
lines.push(' __log("warn", "Not Found", { rid: __rid, method: req.method, path: __pathname, status: 404, ms: Date.now() - __startTime });');
|
|
2515
2659
|
lines.push(' return __notFound;');
|
|
2516
2660
|
|
|
2517
2661
|
if (sessionConfig) {
|
|
@@ -2537,6 +2681,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2537
2681
|
}
|
|
2538
2682
|
lines.push('}');
|
|
2539
2683
|
lines.push('');
|
|
2684
|
+
} // end else (full mode)
|
|
2540
2685
|
|
|
2541
2686
|
// ════════════════════════════════════════════════════════════
|
|
2542
2687
|
// 22. Bun.serve()
|
|
@@ -2615,6 +2760,9 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2615
2760
|
// 24. Graceful Shutdown — on_stop hooks (F3) + clearInterval (F8)
|
|
2616
2761
|
// ════════════════════════════════════════════════════════════
|
|
2617
2762
|
lines.push('// ── Graceful Shutdown ──');
|
|
2763
|
+
if (isFastMode) {
|
|
2764
|
+
lines.push('function __shutdown() { __server.stop(); process.exit(0); }');
|
|
2765
|
+
} else {
|
|
2618
2766
|
lines.push('async function __shutdown() {');
|
|
2619
2767
|
lines.push(` console.log(\`Tova server${label} shutting down...\`);`);
|
|
2620
2768
|
lines.push(' __shuttingDown = true;');
|
|
@@ -2661,6 +2809,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2661
2809
|
lines.push(' if (__logFile) __logFile.end();');
|
|
2662
2810
|
lines.push(' process.exit(0);');
|
|
2663
2811
|
lines.push('}');
|
|
2812
|
+
} // end else (full shutdown)
|
|
2664
2813
|
lines.push('process.on("SIGINT", __shutdown);');
|
|
2665
2814
|
lines.push('process.on("SIGTERM", __shutdown);');
|
|
2666
2815
|
|