blaizejs 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,36 +1,17 @@
1
1
  /**
2
- * blaizejs v0.1.0
3
- * A blazing-fast, type-safe Node.js framework with file-based routing, powerful middleware, and end-to-end type safety
2
+ * blaizejs v0.2.3
3
+ * A blazing-fast, TypeScript-first Node.js framework with HTTP/2 support, file-based routing, powerful middleware system, and end-to-end type safety for building modern APIs.
4
4
  *
5
5
  * Copyright (c) 2025 BlaizeJS Contributors
6
6
  * @license MIT
7
7
  */
8
8
 
9
-
10
- // src/middleware/create.ts
11
- function create(handlerOrOptions) {
12
- if (typeof handlerOrOptions === "function") {
13
- return {
14
- name: "anonymous",
15
- // Default name for function middleware
16
- execute: handlerOrOptions,
17
- debug: false
18
- };
19
- }
20
- const { name = "anonymous", handler, skip, debug = false } = handlerOrOptions;
21
- const middleware = {
22
- name,
23
- execute: handler,
24
- debug
25
- };
26
- if (skip !== void 0) {
27
- return {
28
- ...middleware,
29
- skip
30
- };
31
- }
32
- return middleware;
33
- }
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined") return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
34
15
 
35
16
  // src/middleware/execute.ts
36
17
  function execute(middleware, ctx, next) {
@@ -79,6 +60,31 @@ function compose(middlewareStack) {
79
60
  };
80
61
  }
81
62
 
63
+ // src/middleware/create.ts
64
+ function create(handlerOrOptions) {
65
+ if (typeof handlerOrOptions === "function") {
66
+ return {
67
+ name: "anonymous",
68
+ // Default name for function middleware
69
+ execute: handlerOrOptions,
70
+ debug: false
71
+ };
72
+ }
73
+ const { name = "anonymous", handler, skip, debug = false } = handlerOrOptions;
74
+ const middleware = {
75
+ name,
76
+ execute: handler,
77
+ debug
78
+ };
79
+ if (skip !== void 0) {
80
+ return {
81
+ ...middleware,
82
+ skip
83
+ };
84
+ }
85
+ return middleware;
86
+ }
87
+
82
88
  // src/plugins/create.ts
83
89
  function create2(name, version, setup, defaultOptions = {}) {
84
90
  if (!name || typeof name !== "string") {
@@ -107,48 +113,23 @@ function create2(name, version, setup, defaultOptions = {}) {
107
113
  };
108
114
  }
109
115
 
110
- // src/router/discovery/finder.ts
111
- import * as fs from "node:fs/promises";
112
- import * as path from "node:path";
113
- async function findRouteFiles(routesDir, options = {}) {
114
- const absoluteDir = path.isAbsolute(routesDir) ? routesDir : path.resolve(process.cwd(), routesDir);
115
- console.log("Creating router with routes directory:", absoluteDir);
116
- try {
117
- const stats = await fs.stat(absoluteDir);
118
- if (!stats.isDirectory()) {
119
- throw new Error(`Route directory is not a directory: ${absoluteDir}`);
120
- }
121
- } catch (error) {
122
- if (error.code === "ENOENT") {
123
- throw new Error(`Route directory not found: ${absoluteDir}`);
124
- }
125
- throw error;
126
- }
127
- const routeFiles = [];
128
- const ignore = options.ignore || ["node_modules", ".git"];
129
- async function scanDirectory(dir) {
130
- const entries = await fs.readdir(dir, { withFileTypes: true });
131
- for (const entry of entries) {
132
- const fullPath = path.join(dir, entry.name);
133
- if (entry.isDirectory() && ignore.includes(entry.name)) {
134
- continue;
135
- }
136
- if (entry.isDirectory()) {
137
- await scanDirectory(fullPath);
138
- } else if (isRouteFile(entry.name)) {
139
- routeFiles.push(fullPath);
140
- }
141
- }
142
- }
143
- await scanDirectory(absoluteDir);
144
- return routeFiles;
116
+ // src/router/create.ts
117
+ import { fileURLToPath } from "node:url";
118
+
119
+ // src/config.ts
120
+ var config = {};
121
+ function setRuntimeConfig(newConfig) {
122
+ config = { ...config, ...newConfig };
145
123
  }
146
- function isRouteFile(filename) {
147
- return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
124
+ function getRoutesDir() {
125
+ if (!config.routesDir) {
126
+ throw new Error("Routes directory not configured. Make sure server is properly initialized.");
127
+ }
128
+ return config.routesDir;
148
129
  }
149
130
 
150
131
  // src/router/discovery/parser.ts
151
- import * as path2 from "node:path";
132
+ import * as path from "node:path";
152
133
  function parseRoutePath(filePath, basePath) {
153
134
  if (filePath.startsWith("file://")) {
154
135
  filePath = filePath.replace("file://", "");
@@ -168,7 +149,7 @@ function parseRoutePath(filePath, basePath) {
168
149
  relativePath = relativePath.substring(1);
169
150
  }
170
151
  } else {
171
- relativePath = path2.relative(forwardSlashBasePath, forwardSlashFilePath).replace(/\\/g, "/");
152
+ relativePath = path.relative(forwardSlashBasePath, forwardSlashFilePath).replace(/\\/g, "/");
172
153
  }
173
154
  relativePath = relativePath.replace(/\.[^.]+$/, "");
174
155
  const segments = relativePath.split("/").filter(Boolean);
@@ -192,1697 +173,2137 @@ function parseRoutePath(filePath, basePath) {
192
173
  };
193
174
  }
194
175
 
195
- // src/router/discovery/loader.ts
196
- async function dynamicImport(filePath) {
197
- return import(filePath);
198
- }
199
- async function loadRouteModule(filePath, basePath) {
176
+ // src/router/create.ts
177
+ function getCallerFilePath() {
178
+ const originalPrepareStackTrace = Error.prepareStackTrace;
200
179
  try {
201
- const parsedRoute = parseRoutePath(filePath, basePath);
202
- console.log("parsedRoute:", parsedRoute);
203
- const module = await dynamicImport(filePath);
204
- console.log("Module exports:", Object.keys(module));
205
- const routes = [];
206
- if (module.default && typeof module.default === "object") {
207
- console.log("Found default export:", module.default);
208
- const route = {
209
- ...module.default,
210
- path: parsedRoute.routePath
211
- };
212
- routes.push(route);
180
+ Error.prepareStackTrace = (_, stack2) => stack2;
181
+ const stack = new Error().stack;
182
+ const callerFrame = stack[3];
183
+ if (!callerFrame || typeof callerFrame.getFileName !== "function") {
184
+ throw new Error("Unable to determine caller file frame");
213
185
  }
214
- Object.entries(module).forEach(([exportName, exportValue]) => {
215
- if (exportName === "default" || !exportValue || typeof exportValue !== "object") {
216
- return;
217
- }
218
- const potentialRoute = exportValue;
219
- if (isValidRoute(potentialRoute)) {
220
- console.log(`Found named route export: ${exportName}`, potentialRoute);
221
- const route = {
222
- ...potentialRoute,
223
- // Use the route's own path if it has one, otherwise derive from file
224
- path: parsedRoute.routePath
225
- };
226
- routes.push(route);
227
- }
228
- });
229
- if (routes.length === 0) {
230
- console.warn(`Route file ${filePath} does not export any valid route definitions`);
231
- return [];
186
+ const fileName = callerFrame.getFileName();
187
+ if (!fileName) {
188
+ throw new Error("Unable to determine caller file name");
232
189
  }
233
- console.log(`Loaded ${routes.length} route(s) from ${filePath}`);
234
- return routes;
235
- } catch (error) {
236
- console.error(`Failed to load route module ${filePath}:`, error);
237
- return [];
190
+ if (fileName.startsWith("file://")) {
191
+ return fileURLToPath(fileName);
192
+ }
193
+ return fileName;
194
+ } finally {
195
+ Error.prepareStackTrace = originalPrepareStackTrace;
238
196
  }
239
197
  }
240
- function isValidRoute(obj) {
241
- if (!obj || typeof obj !== "object") {
242
- return false;
243
- }
244
- const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
245
- const hasHttpMethod = httpMethods.some(
246
- (method) => obj[method] && typeof obj[method] === "object" && obj[method].handler
247
- );
248
- return hasHttpMethod;
198
+ function getRoutePath() {
199
+ const callerPath = getCallerFilePath();
200
+ const routesDir = getRoutesDir();
201
+ const parsedRoute = parseRoutePath(callerPath, routesDir);
202
+ console.log(`\u{1F50E} Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
203
+ return parsedRoute.routePath;
249
204
  }
250
-
251
- // src/router/discovery/index.ts
252
- async function findRoutes(routesDir, options = {}) {
253
- const routeFiles = await findRouteFiles(routesDir, {
254
- ignore: options.ignore
255
- });
256
- const routes = [];
257
- for (const filePath of routeFiles) {
258
- const moduleRoutes = await loadRouteModule(filePath, routesDir);
259
- if (moduleRoutes.length > 0) {
260
- routes.push(...moduleRoutes);
261
- }
205
+ var createGetRoute = (config2) => {
206
+ validateMethodConfig("GET", config2);
207
+ const path6 = getRoutePath();
208
+ return {
209
+ GET: config2,
210
+ path: path6
211
+ };
212
+ };
213
+ var createPostRoute = (config2) => {
214
+ validateMethodConfig("POST", config2);
215
+ const path6 = getRoutePath();
216
+ return {
217
+ POST: config2,
218
+ path: path6
219
+ };
220
+ };
221
+ var createPutRoute = (config2) => {
222
+ validateMethodConfig("PUT", config2);
223
+ const path6 = getRoutePath();
224
+ return {
225
+ PUT: config2,
226
+ path: path6
227
+ };
228
+ };
229
+ var createDeleteRoute = (config2) => {
230
+ validateMethodConfig("DELETE", config2);
231
+ const path6 = getRoutePath();
232
+ return {
233
+ DELETE: config2,
234
+ path: path6
235
+ };
236
+ };
237
+ var createPatchRoute = (config2) => {
238
+ validateMethodConfig("PATCH", config2);
239
+ const path6 = getRoutePath();
240
+ return {
241
+ PATCH: config2,
242
+ path: path6
243
+ };
244
+ };
245
+ var createHeadRoute = (config2) => {
246
+ validateMethodConfig("HEAD", config2);
247
+ const path6 = getRoutePath();
248
+ return {
249
+ HEAD: config2,
250
+ path: path6
251
+ };
252
+ };
253
+ var createOptionsRoute = (config2) => {
254
+ validateMethodConfig("OPTIONS", config2);
255
+ const path6 = getRoutePath();
256
+ return {
257
+ OPTIONS: config2,
258
+ path: path6
259
+ };
260
+ };
261
+ function validateMethodConfig(method, config2) {
262
+ if (!config2.handler || typeof config2.handler !== "function") {
263
+ throw new Error(`Handler for method ${method} must be a function`);
262
264
  }
263
- return routes;
264
- }
265
-
266
- // src/router/discovery/watchers.ts
267
- import * as path3 from "node:path";
268
- import { watch } from "chokidar";
269
- function watchRoutes(routesDir, options = {}) {
270
- const routesByPath = /* @__PURE__ */ new Map();
271
- async function loadInitialRoutes() {
272
- try {
273
- const files = await findRouteFiles(routesDir, {
274
- ignore: options.ignore
275
- });
276
- for (const filePath of files) {
277
- await loadAndNotify(filePath);
278
- }
279
- } catch (error) {
280
- handleError(error);
281
- }
265
+ if (config2.middleware && !Array.isArray(config2.middleware)) {
266
+ throw new Error(`Middleware for method ${method} must be an array`);
282
267
  }
283
- async function loadAndNotify(filePath) {
284
- try {
285
- const routes = await loadRouteModule(filePath, routesDir);
286
- if (!routes || routes.length === 0) {
287
- return;
288
- }
289
- const existingRoutes = routesByPath.get(filePath);
290
- if (existingRoutes) {
291
- routesByPath.set(filePath, routes);
292
- if (options.onRouteChanged) {
293
- options.onRouteChanged(routes);
294
- }
295
- } else {
296
- routesByPath.set(filePath, routes);
297
- if (options.onRouteAdded) {
298
- options.onRouteAdded(routes);
299
- }
268
+ if (config2.schema) {
269
+ validateSchema(method, config2.schema);
270
+ }
271
+ switch (method) {
272
+ case "GET":
273
+ case "HEAD":
274
+ case "DELETE":
275
+ if (config2.schema?.body) {
276
+ console.warn(`Warning: ${method} requests typically don't have request bodies`);
300
277
  }
301
- } catch (error) {
302
- handleError(error);
303
- }
278
+ break;
304
279
  }
305
- function handleRemoved(filePath) {
306
- const normalizedPath = path3.normalize(filePath);
307
- const routes = routesByPath.get(normalizedPath);
308
- if (routes && routes.length > 0 && options.onRouteRemoved) {
309
- options.onRouteRemoved(normalizedPath, routes);
310
- }
311
- routesByPath.delete(normalizedPath);
280
+ }
281
+ function validateSchema(method, schema) {
282
+ const { params, query, body, response } = schema;
283
+ if (params && (!params._def || typeof params.parse !== "function")) {
284
+ throw new Error(`Params schema for ${method} must be a valid Zod schema`);
312
285
  }
313
- function handleError(error) {
314
- if (options.onError && error instanceof Error) {
315
- options.onError(error);
316
- } else {
317
- console.error("Route watcher error:", error);
318
- }
286
+ if (query && (!query._def || typeof query.parse !== "function")) {
287
+ throw new Error(`Query schema for ${method} must be a valid Zod schema`);
319
288
  }
320
- const watcher = watch(routesDir, {
321
- ignored: [
322
- /(^|[/\\])\../,
323
- // Ignore dot files
324
- /node_modules/,
325
- ...options.ignore || []
326
- ],
327
- persistent: true,
328
- ignoreInitial: false,
329
- awaitWriteFinish: {
330
- stabilityThreshold: 300,
331
- pollInterval: 100
332
- }
333
- });
334
- watcher.on("add", loadAndNotify).on("change", loadAndNotify).on("unlink", handleRemoved).on("error", handleError);
335
- loadInitialRoutes().catch(handleError);
336
- return {
337
- /**
338
- * Close the watcher
339
- */
340
- close: () => watcher.close(),
341
- /**
342
- * Get all currently loaded routes (flattened)
343
- */
344
- getRoutes: () => {
345
- const allRoutes = [];
346
- for (const routes of routesByPath.values()) {
347
- allRoutes.push(...routes);
348
- }
349
- return allRoutes;
350
- },
351
- /**
352
- * Get routes organized by file path
353
- */
354
- getRoutesByFile: () => new Map(routesByPath)
355
- };
356
- }
357
-
358
- // src/router/handlers/error.ts
359
- function handleRouteError(ctx, error, options = {}) {
360
- if (options.log) {
361
- console.error("Route error:", error);
289
+ if (body && (!body._def || typeof body.parse !== "function")) {
290
+ throw new Error(`Body schema for ${method} must be a valid Zod schema`);
362
291
  }
363
- const status = getErrorStatus(error);
364
- const response = {
365
- error: getErrorType(error),
366
- message: getErrorMessage(error)
367
- };
368
- if (options.detailed) {
369
- if (error instanceof Error) {
370
- response.stack = error.stack;
371
- }
372
- if (error && typeof error === "object" && "details" in error && error.details) {
373
- response.details = error.details;
374
- }
292
+ if (response && (!response._def || typeof response.parse !== "function")) {
293
+ throw new Error(`Response schema for ${method} must be a valid Zod schema`);
375
294
  }
376
- ctx.response.status(status).json(response);
377
295
  }
378
- function getErrorStatus(error) {
379
- if (error && typeof error === "object") {
380
- if ("status" in error && typeof error.status === "number") {
381
- return error.status;
382
- }
383
- if ("statusCode" in error && typeof error.statusCode === "number") {
384
- return error.statusCode;
385
- }
386
- if ("code" in error && typeof error.code === "string") {
387
- return getStatusFromCode(error.code);
388
- }
296
+
297
+ // src/server/create.ts
298
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
299
+ import EventEmitter from "node:events";
300
+
301
+ // src/server/start.ts
302
+ import * as fs2 from "node:fs";
303
+ import * as http from "node:http";
304
+ import * as http2 from "node:http2";
305
+
306
+ // src/server/dev-certificate.ts
307
+ import * as fs from "node:fs";
308
+ import * as path2 from "node:path";
309
+ import * as selfsigned from "selfsigned";
310
+ async function generateDevCertificates() {
311
+ const certDir = path2.join(process.cwd(), ".blaizejs", "certs");
312
+ const keyPath = path2.join(certDir, "dev.key");
313
+ const certPath = path2.join(certDir, "dev.cert");
314
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
315
+ return {
316
+ keyFile: keyPath,
317
+ certFile: certPath
318
+ };
389
319
  }
390
- return 500;
391
- }
392
- function getStatusFromCode(code) {
393
- switch (code) {
394
- case "NOT_FOUND":
395
- return 404;
396
- case "UNAUTHORIZED":
397
- return 401;
398
- case "FORBIDDEN":
399
- return 403;
400
- case "BAD_REQUEST":
401
- return 400;
402
- case "CONFLICT":
403
- return 409;
404
- default:
405
- return 500;
320
+ if (!fs.existsSync(certDir)) {
321
+ fs.mkdirSync(certDir, { recursive: true });
406
322
  }
323
+ const attrs = [{ name: "commonName", value: "localhost" }];
324
+ const options = {
325
+ days: 365,
326
+ algorithm: "sha256",
327
+ keySize: 2048,
328
+ extensions: [
329
+ { name: "basicConstraints", cA: true },
330
+ {
331
+ name: "keyUsage",
332
+ keyCertSign: true,
333
+ digitalSignature: true,
334
+ nonRepudiation: true,
335
+ keyEncipherment: true,
336
+ dataEncipherment: true
337
+ },
338
+ {
339
+ name: "extKeyUsage",
340
+ serverAuth: true,
341
+ clientAuth: true
342
+ },
343
+ {
344
+ name: "subjectAltName",
345
+ altNames: [
346
+ { type: 2, value: "localhost" },
347
+ { type: 7, ip: "127.0.0.1" }
348
+ ]
349
+ }
350
+ ]
351
+ };
352
+ const pems = selfsigned.generate(attrs, options);
353
+ fs.writeFileSync(keyPath, Buffer.from(pems.private, "utf-8"));
354
+ fs.writeFileSync(certPath, Buffer.from(pems.cert, "utf-8"));
355
+ console.log(`
356
+ \u{1F512} Generated self-signed certificates for development at ${certDir}
357
+ `);
358
+ return {
359
+ keyFile: keyPath,
360
+ certFile: certPath
361
+ };
407
362
  }
408
- function getErrorType(error) {
409
- if (error && typeof error === "object") {
410
- if ("type" in error && typeof error.type === "string") {
411
- return error.type;
412
- }
413
- if ("name" in error && typeof error.name === "string") {
414
- return error.name;
415
- }
416
- if (error instanceof Error) {
417
- return error.constructor.name;
418
- }
363
+
364
+ // src/context/errors.ts
365
+ var ResponseSentError = class extends Error {
366
+ constructor(message = "\u274C Response has already been sent") {
367
+ super(message);
368
+ this.name = "ResponseSentError";
419
369
  }
420
- return "Error";
421
- }
422
- function getErrorMessage(error) {
423
- if (error instanceof Error) {
424
- return error.message;
370
+ };
371
+ var ResponseSentHeaderError = class extends ResponseSentError {
372
+ constructor(message = "Cannot set header after response has been sent") {
373
+ super(message);
425
374
  }
426
- if (error && typeof error === "object") {
427
- if ("message" in error && typeof error.message === "string") {
428
- return error.message;
429
- }
375
+ };
376
+ var ResponseSentContentError = class extends ResponseSentError {
377
+ constructor(message = "Cannot set content type after response has been sent") {
378
+ super(message);
430
379
  }
431
- return String(error);
432
- }
433
-
434
- // src/router/validation/body.ts
435
- import { z } from "zod";
436
- function validateBody(body, schema) {
437
- if (schema instanceof z.ZodObject) {
438
- return schema.strict().parse(body);
380
+ };
381
+ var ParseUrlError = class extends ResponseSentError {
382
+ constructor(message = "Invalide URL") {
383
+ super(message);
439
384
  }
440
- return schema.parse(body);
441
- }
385
+ };
442
386
 
443
- // src/router/validation/params.ts
444
- import { z as z2 } from "zod";
445
- function validateParams(params, schema) {
446
- if (schema instanceof z2.ZodObject) {
447
- return schema.strict().parse(params);
448
- }
449
- return schema.parse(params);
387
+ // src/context/store.ts
388
+ import { AsyncLocalStorage } from "node:async_hooks";
389
+ var contextStorage = new AsyncLocalStorage();
390
+ function runWithContext(context, callback) {
391
+ return contextStorage.run(context, callback);
450
392
  }
451
393
 
452
- // src/router/validation/query.ts
453
- import { z as z3 } from "zod";
454
- function validateQuery(query, schema) {
455
- if (schema instanceof z3.ZodObject) {
456
- return schema.strict().parse(query);
394
+ // src/context/create.ts
395
+ var CONTENT_TYPE_HEADER = "Content-Type";
396
+ function parseRequestUrl(req) {
397
+ const originalUrl = req.url || "/";
398
+ const host = req.headers.host || "localhost";
399
+ const protocol = req.socket && req.socket.encrypted ? "https" : "http";
400
+ const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
401
+ try {
402
+ const url = new URL(fullUrl);
403
+ const path6 = url.pathname;
404
+ const query = {};
405
+ url.searchParams.forEach((value, key) => {
406
+ if (query[key] !== void 0) {
407
+ if (Array.isArray(query[key])) {
408
+ query[key].push(value);
409
+ } else {
410
+ query[key] = [query[key], value];
411
+ }
412
+ } else {
413
+ query[key] = value;
414
+ }
415
+ });
416
+ return { path: path6, url, query };
417
+ } catch (error) {
418
+ console.warn(`Invalid URL: ${fullUrl}`, error);
419
+ throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
457
420
  }
458
- return schema.parse(query);
459
421
  }
460
-
461
- // src/router/validation/response.ts
462
- import { z as z4 } from "zod";
463
- function validateResponse(response, schema) {
464
- if (schema instanceof z4.ZodObject) {
465
- return schema.strict().parse(response);
422
+ function isHttp2Request(req) {
423
+ return "stream" in req || "httpVersionMajor" in req && req.httpVersionMajor === 2;
424
+ }
425
+ function getProtocol(req) {
426
+ const encrypted = req.socket && req.socket.encrypted;
427
+ const forwardedProto = req.headers["x-forwarded-proto"];
428
+ if (forwardedProto) {
429
+ if (Array.isArray(forwardedProto)) {
430
+ return forwardedProto[0]?.split(",")[0]?.trim() || "http";
431
+ } else {
432
+ return forwardedProto.split(",")[0]?.trim() || "http";
433
+ }
466
434
  }
467
- return schema.parse(response);
435
+ return encrypted ? "https" : "http";
468
436
  }
469
-
470
- // src/router/validation/schema.ts
471
- function createRequestValidator(schema, debug = false) {
472
- const middlewareFn = async (ctx, next) => {
473
- const errors = {};
474
- if (schema.params && ctx.request.params) {
475
- try {
476
- ctx.request.params = validateParams(ctx.request.params, schema.params);
477
- } catch (error) {
478
- errors.params = formatValidationError(error);
479
- }
480
- }
481
- if (schema.query && ctx.request.query) {
482
- try {
483
- ctx.request.query = validateQuery(ctx.request.query, schema.query);
484
- } catch (error) {
485
- errors.query = formatValidationError(error);
486
- }
487
- }
488
- if (schema.body) {
489
- try {
490
- ctx.request.body = validateBody(ctx.request.body, schema.body);
491
- } catch (error) {
492
- errors.body = formatValidationError(error);
493
- }
437
+ async function createContext(req, res, options = {}) {
438
+ const { path: path6, url, query } = parseRequestUrl(req);
439
+ const method = req.method || "GET";
440
+ const isHttp2 = isHttp2Request(req);
441
+ const protocol = getProtocol(req);
442
+ const params = {};
443
+ const state = { ...options.initialState || {} };
444
+ const responseState = { sent: false };
445
+ const ctx = {
446
+ request: createRequestObject(req, {
447
+ path: path6,
448
+ url,
449
+ query,
450
+ params,
451
+ method,
452
+ isHttp2,
453
+ protocol
454
+ }),
455
+ response: {},
456
+ state
457
+ };
458
+ ctx.response = createResponseObject(res, responseState, ctx);
459
+ if (options.parseBody) {
460
+ await parseBodyIfNeeded(req, ctx);
461
+ }
462
+ return ctx;
463
+ }
464
+ function createRequestObject(req, info) {
465
+ return {
466
+ raw: req,
467
+ ...info,
468
+ header: createRequestHeaderGetter(req),
469
+ headers: createRequestHeadersGetter(req),
470
+ body: void 0
471
+ };
472
+ }
473
+ function createRequestHeaderGetter(req) {
474
+ return (name) => {
475
+ const value = req.headers[name.toLowerCase()];
476
+ if (Array.isArray(value)) {
477
+ return value.join(", ");
494
478
  }
495
- if (Object.keys(errors).length > 0) {
496
- ctx.response.status(400).json({
497
- error: "Validation Error",
498
- details: errors
499
- });
500
- return;
479
+ return value || void 0;
480
+ };
481
+ }
482
+ function createRequestHeadersGetter(req) {
483
+ const headerGetter = createRequestHeaderGetter(req);
484
+ return (names) => {
485
+ if (names && Array.isArray(names) && names.length > 0) {
486
+ return names.reduce((acc, name) => {
487
+ acc[name] = headerGetter(name);
488
+ return acc;
489
+ }, {});
490
+ } else {
491
+ return Object.entries(req.headers).reduce(
492
+ (acc, [key, value]) => {
493
+ acc[key] = Array.isArray(value) ? value.join(", ") : value || void 0;
494
+ return acc;
495
+ },
496
+ {}
497
+ );
501
498
  }
502
- await next();
503
499
  };
500
+ }
501
+ function createResponseObject(res, responseState, ctx) {
504
502
  return {
505
- name: "RequestValidator",
506
- execute: middlewareFn,
507
- debug
503
+ raw: res,
504
+ get sent() {
505
+ return responseState.sent;
506
+ },
507
+ status: createStatusSetter(res, responseState, ctx),
508
+ header: createHeaderSetter(res, responseState, ctx),
509
+ headers: createHeadersSetter(res, responseState, ctx),
510
+ type: createContentTypeSetter(res, responseState, ctx),
511
+ json: createJsonResponder(res, responseState),
512
+ text: createTextResponder(res, responseState),
513
+ html: createHtmlResponder(res, responseState),
514
+ redirect: createRedirectResponder(res, responseState),
515
+ stream: createStreamResponder(res, responseState)
508
516
  };
509
517
  }
510
- function createResponseValidator(responseSchema, debug = false) {
511
- const middlewareFn = async (ctx, next) => {
512
- const originalJson = ctx.response.json;
513
- ctx.response.json = (body, status) => {
514
- try {
515
- const validatedBody = validateResponse(body, responseSchema);
516
- ctx.response.json = originalJson;
517
- return originalJson.call(ctx.response, validatedBody, status);
518
- } catch (error) {
519
- ctx.response.json = originalJson;
520
- console.error("Response validation error:", error);
521
- ctx.response.status(500).json({
522
- error: "Internal Server Error",
523
- message: "Response validation failed"
524
- });
525
- return ctx.response;
526
- }
527
- };
528
- await next();
518
+ function createStatusSetter(res, responseState, ctx) {
519
+ return function statusSetter(code) {
520
+ if (responseState.sent) {
521
+ throw new ResponseSentError();
522
+ }
523
+ res.statusCode = code;
524
+ return ctx.response;
529
525
  };
530
- return {
531
- name: "ResponseValidator",
532
- execute: middlewareFn,
533
- debug
526
+ }
527
+ function createHeaderSetter(res, responseState, ctx) {
528
+ return function headerSetter(name, value) {
529
+ if (responseState.sent) {
530
+ throw new ResponseSentHeaderError();
531
+ }
532
+ res.setHeader(name, value);
533
+ return ctx.response;
534
534
  };
535
535
  }
536
- function formatValidationError(error) {
537
- if (error && typeof error === "object" && "format" in error && typeof error.format === "function") {
538
- return error.format();
539
- }
540
- return error instanceof Error ? error.message : String(error);
536
+ function createHeadersSetter(res, responseState, ctx) {
537
+ return function headersSetter(headers) {
538
+ if (responseState.sent) {
539
+ throw new ResponseSentHeaderError();
540
+ }
541
+ for (const [name, value] of Object.entries(headers)) {
542
+ res.setHeader(name, value);
543
+ }
544
+ return ctx.response;
545
+ };
541
546
  }
542
-
543
- // src/router/handlers/executor.ts
544
- async function executeHandler(ctx, routeOptions, params) {
545
- const middleware = [...routeOptions.middleware || []];
546
- if (routeOptions.schema) {
547
- if (routeOptions.schema.params || routeOptions.schema.query || routeOptions.schema.body) {
548
- middleware.unshift(createRequestValidator(routeOptions.schema));
547
+ function createContentTypeSetter(res, responseState, ctx) {
548
+ return function typeSetter(type) {
549
+ if (responseState.sent) {
550
+ throw new ResponseSentContentError();
549
551
  }
550
- if (routeOptions.schema.response) {
551
- middleware.push(createResponseValidator(routeOptions.schema.response));
552
+ res.setHeader(CONTENT_TYPE_HEADER, type);
553
+ return ctx.response;
554
+ };
555
+ }
556
+ function createJsonResponder(res, responseState) {
557
+ return function jsonResponder(body, status) {
558
+ if (responseState.sent) {
559
+ throw new ResponseSentError();
552
560
  }
553
- }
554
- const handler = compose([...middleware]);
555
- await handler(ctx, async () => {
556
- const result = await routeOptions.handler(ctx, params);
557
- if (!ctx.response.sent && result !== void 0) {
558
- ctx.response.json(result);
561
+ if (status !== void 0) {
562
+ res.statusCode = status;
559
563
  }
560
- });
564
+ res.setHeader(CONTENT_TYPE_HEADER, "application/json");
565
+ res.end(JSON.stringify(body));
566
+ responseState.sent = true;
567
+ };
561
568
  }
562
-
563
- // src/router/matching/params.ts
564
- function extractParams(path5, pattern, paramNames) {
565
- const match = pattern.exec(path5);
566
- if (!match) {
567
- return {};
568
- }
569
- const params = {};
570
- for (let i = 0; i < paramNames.length; i++) {
571
- params[paramNames[i]] = match[i + 1] || "";
572
- }
573
- return params;
569
+ function createTextResponder(res, responseState) {
570
+ return function textResponder(body, status) {
571
+ if (responseState.sent) {
572
+ throw new ResponseSentError();
573
+ }
574
+ if (status !== void 0) {
575
+ res.statusCode = status;
576
+ }
577
+ res.setHeader(CONTENT_TYPE_HEADER, "text/plain");
578
+ res.end(body);
579
+ responseState.sent = true;
580
+ };
574
581
  }
575
- function compilePathPattern(path5) {
576
- const paramNames = [];
577
- if (path5 === "/") {
578
- return {
579
- pattern: /^\/$/,
580
- paramNames: []
581
- };
582
- }
583
- let patternString = path5.replace(/([.+*?^$(){}|\\])/g, "\\$1");
584
- patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
585
- paramNames.push(paramName);
586
- return "/([^/]+)";
587
- }).replace(/\/\[([^\]]+)\]/g, (_, paramName) => {
588
- paramNames.push(paramName);
589
- return "/([^/]+)";
590
- });
591
- patternString = `${patternString}(?:/)?`;
592
- const pattern = new RegExp(`^${patternString}$`);
593
- return {
594
- pattern,
595
- paramNames
582
+ function createHtmlResponder(res, responseState) {
583
+ return function htmlResponder(body, status) {
584
+ if (responseState.sent) {
585
+ throw new ResponseSentError();
586
+ }
587
+ if (status !== void 0) {
588
+ res.statusCode = status;
589
+ }
590
+ res.setHeader(CONTENT_TYPE_HEADER, "text/html");
591
+ res.end(body);
592
+ responseState.sent = true;
596
593
  };
597
594
  }
598
-
599
- // src/router/matching/matcher.ts
600
- function createMatcher() {
601
- const routes = [];
602
- return {
603
- /**
604
- * Add a route to the matcher
605
- */
606
- add(path5, method, routeOptions) {
607
- const { pattern, paramNames } = compilePathPattern(path5);
608
- const newRoute = {
609
- path: path5,
610
- method,
611
- pattern,
612
- paramNames,
613
- routeOptions
614
- };
615
- const insertIndex = routes.findIndex((route) => paramNames.length < route.paramNames.length);
616
- if (insertIndex === -1) {
617
- routes.push(newRoute);
618
- } else {
619
- routes.splice(insertIndex, 0, newRoute);
620
- }
621
- },
622
- /**
623
- * Match a URL path to a route
624
- */
625
- match(path5, method) {
626
- const pathname = path5.split("?")[0];
627
- if (!pathname) return null;
628
- for (const route of routes) {
629
- if (route.method !== method) continue;
630
- const match = route.pattern.exec(pathname);
631
- if (match) {
632
- const params = extractParams(path5, route.pattern, route.paramNames);
633
- return {
634
- route: route.routeOptions,
635
- params
636
- };
637
- }
638
- }
639
- const matchingPath = routes.find(
640
- (route) => route.method !== method && route.pattern.test(path5)
641
- );
642
- if (matchingPath) {
643
- return {
644
- route: null,
645
- params: {},
646
- methodNotAllowed: true,
647
- allowedMethods: routes.filter((route) => route.pattern.test(path5)).map((route) => route.method)
648
- };
649
- }
650
- return null;
651
- },
652
- /**
653
- * Get all registered routes
654
- */
655
- getRoutes() {
656
- return routes.map((route) => ({
657
- path: route.path,
658
- method: route.method
659
- }));
660
- },
661
- /**
662
- * Find routes matching a specific path
663
- */
664
- findRoutes(path5) {
665
- return routes.filter((route) => route.pattern.test(path5)).map((route) => ({
666
- path: route.path,
667
- method: route.method,
668
- params: extractParams(path5, route.pattern, route.paramNames)
669
- }));
595
+ function createRedirectResponder(res, responseState) {
596
+ return function redirectResponder(url, status = 302) {
597
+ if (responseState.sent) {
598
+ throw new ResponseSentError();
670
599
  }
600
+ res.statusCode = status;
601
+ res.setHeader("Location", url);
602
+ res.end();
603
+ responseState.sent = true;
671
604
  };
672
605
  }
673
-
674
- // src/router/router.ts
675
- var DEFAULT_ROUTER_OPTIONS = {
676
- routesDir: "./routes",
677
- basePath: "/",
678
- watchMode: process.env.NODE_ENV === "development"
679
- };
680
- function createRouter(options) {
681
- const routerOptions = {
682
- ...DEFAULT_ROUTER_OPTIONS,
683
- ...options
684
- };
685
- if (options.basePath && !options.basePath.startsWith("/")) {
686
- console.warn("Base path does nothing");
687
- }
688
- const routes = [];
689
- const matcher = createMatcher();
690
- let initialized = false;
691
- let initializationPromise = null;
692
- let _watchers = null;
693
- const routeSources = /* @__PURE__ */ new Map();
694
- const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
695
- function addRouteWithSource(route, source) {
696
- const existingSources = routeSources.get(route.path) || [];
697
- if (existingSources.includes(source)) {
698
- console.warn(`Skipping duplicate route: ${route.path} from ${source}`);
699
- return;
700
- }
701
- if (existingSources.length > 0) {
702
- const conflictError = new Error(
703
- `Route conflict for path "${route.path}": already defined in ${existingSources.join(", ")}, now being added from ${source}`
704
- );
705
- console.error(conflictError.message);
706
- throw conflictError;
606
+ function createStreamResponder(res, responseState) {
607
+ return function streamResponder(readable, options = {}) {
608
+ if (responseState.sent) {
609
+ throw new ResponseSentError();
707
610
  }
708
- routeSources.set(route.path, [...existingSources, source]);
709
- addRouteInternal(route);
710
- }
711
- async function loadRoutesFromDirectory(directory, source, prefix) {
712
- try {
713
- const discoveredRoutes = await findRoutes(directory, {
714
- basePath: routerOptions.basePath
715
- });
716
- for (const route of discoveredRoutes) {
717
- const finalRoute = prefix ? {
718
- ...route,
719
- path: `${prefix}${route.path}`
720
- } : route;
721
- addRouteWithSource(finalRoute, source);
722
- }
723
- console.log(
724
- `Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
725
- );
726
- } catch (error) {
727
- console.error(`Failed to load routes from ${source}:`, error);
728
- throw error;
611
+ if (options.status !== void 0) {
612
+ res.statusCode = options.status;
729
613
  }
730
- }
731
- async function initialize() {
732
- if (initialized || initializationPromise) {
733
- return initializationPromise;
614
+ if (options.contentType) {
615
+ res.setHeader(CONTENT_TYPE_HEADER, options.contentType);
734
616
  }
735
- initializationPromise = (async () => {
736
- try {
737
- for (const directory of routeDirectories) {
738
- await loadRoutesFromDirectory(directory, directory);
739
- }
740
- if (routerOptions.watchMode) {
741
- setupWatcherForAllDirectories();
742
- }
743
- initialized = true;
744
- } catch (error) {
745
- console.error("Failed to initialize router:", error);
746
- throw error;
617
+ if (options.headers) {
618
+ for (const [name, value] of Object.entries(options.headers)) {
619
+ res.setHeader(name, value);
747
620
  }
748
- })();
749
- return initializationPromise;
750
- }
751
- function addRouteInternal(route) {
752
- routes.push(route);
753
- Object.entries(route).forEach(([method, methodOptions]) => {
754
- if (method === "path" || !methodOptions) return;
755
- matcher.add(route.path, method, methodOptions);
621
+ }
622
+ readable.pipe(res);
623
+ readable.on("end", () => {
624
+ responseState.sent = true;
756
625
  });
757
- }
758
- function createWatcherCallbacks(directory, source, prefix) {
759
- return {
760
- onRouteAdded: (addedRoutes) => {
761
- console.log(
762
- `${addedRoutes.length} route(s) added from ${directory}:`,
763
- addedRoutes.map((r) => r.path)
764
- );
765
- addedRoutes.forEach((route) => {
766
- const finalRoute = prefix ? { ...route, path: `${prefix}${route.path}` } : route;
767
- addRouteWithSource(finalRoute, source);
768
- });
769
- },
770
- onRouteChanged: (changedRoutes) => {
771
- console.log(
772
- `${changedRoutes.length} route(s) changed in ${directory}:`,
773
- changedRoutes.map((r) => r.path)
774
- );
775
- changedRoutes.forEach((route) => {
776
- const finalPath = prefix ? `${prefix}${route.path}` : route.path;
777
- const index = routes.findIndex((r) => r.path === finalPath);
778
- if (index >= 0) {
779
- routes.splice(index, 1);
780
- const sources = routeSources.get(finalPath) || [];
781
- const filteredSources = sources.filter((s) => s !== source);
782
- if (filteredSources.length > 0) {
783
- routeSources.set(finalPath, filteredSources);
784
- } else {
785
- routeSources.delete(finalPath);
786
- }
787
- }
788
- const finalRoute = prefix ? { ...route, path: finalPath } : route;
789
- addRouteWithSource(finalRoute, source);
790
- });
791
- },
792
- onRouteRemoved: (filePath, removedRoutes) => {
793
- console.log(
794
- `File removed from ${directory}: ${filePath} with ${removedRoutes.length} route(s):`,
795
- removedRoutes.map((r) => r.path)
796
- );
797
- removedRoutes.forEach((route) => {
798
- const finalPath = prefix ? `${prefix}${route.path}` : route.path;
799
- const index = routes.findIndex((r) => r.path === finalPath);
800
- if (index >= 0) {
801
- routes.splice(index, 1);
802
- }
803
- const sources = routeSources.get(finalPath) || [];
804
- const filteredSources = sources.filter((s) => s !== source);
805
- if (filteredSources.length > 0) {
806
- routeSources.set(finalPath, filteredSources);
807
- } else {
808
- routeSources.delete(finalPath);
809
- }
810
- });
811
- },
812
- onError: (error) => {
813
- console.error(`Route watcher error for ${directory}:`, error);
626
+ readable.on("error", (err) => {
627
+ console.error("Stream error:", err);
628
+ if (!responseState.sent) {
629
+ res.statusCode = 500;
630
+ res.end("Stream error");
631
+ responseState.sent = true;
814
632
  }
815
- };
816
- }
817
- function setupWatcherForDirectory(directory, source, prefix) {
818
- const callbacks = createWatcherCallbacks(directory, source, prefix);
819
- const watcher = watchRoutes(directory, {
820
- ignore: ["node_modules", ".git"],
821
- ...callbacks
822
633
  });
823
- if (!_watchers) {
824
- _watchers = /* @__PURE__ */ new Map();
825
- }
826
- _watchers.set(directory, watcher);
827
- return watcher;
634
+ };
635
+ }
636
+ async function parseBodyIfNeeded(req, ctx) {
637
+ if (shouldSkipParsing(req.method)) {
638
+ return;
828
639
  }
829
- function setupWatcherForAllDirectories() {
830
- for (const directory of routeDirectories) {
831
- setupWatcherForDirectory(directory, directory);
832
- }
640
+ const contentType = req.headers["content-type"] || "";
641
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
642
+ if (contentLength === 0 || contentLength > 1048576) {
643
+ return;
644
+ }
645
+ try {
646
+ await parseBodyByContentType(req, ctx, contentType);
647
+ } catch (error) {
648
+ setBodyError(ctx, "body_read_error", "Error reading request body", error);
833
649
  }
834
- initialize().catch((error) => {
835
- console.error("Failed to initialize router on creation:", error);
836
- });
837
- return {
838
- /**
839
- * Handle an incoming request
840
- */
841
- async handleRequest(ctx) {
842
- if (!initialized) {
843
- await initialize();
844
- }
845
- const { method, path: path5 } = ctx.request;
846
- const match = matcher.match(path5, method);
847
- if (!match) {
848
- ctx.response.status(404).json({ error: "Not Found" });
849
- return;
850
- }
851
- if (match.methodNotAllowed) {
852
- ctx.response.status(405).json({
853
- error: "Method Not Allowed",
854
- allowed: match.allowedMethods
855
- });
856
- if (match.allowedMethods && match.allowedMethods.length > 0) {
857
- ctx.response.header("Allow", match.allowedMethods.join(", "));
858
- }
859
- return;
860
- }
861
- ctx.request.params = match.params;
862
- try {
863
- await executeHandler(ctx, match.route, match.params);
864
- } catch (error) {
865
- handleRouteError(ctx, error, {
866
- detailed: process.env.NODE_ENV !== "production",
867
- log: true
868
- });
869
- }
870
- },
871
- /**
872
- * Get all registered routes
873
- */
874
- getRoutes() {
875
- return [...routes];
876
- },
877
- /**
878
- * Add a route programmatically
879
- */
880
- addRoute(route) {
881
- addRouteInternal(route);
882
- },
883
- /**
884
- * Add a route directory (for plugins)
885
- */
886
- async addRouteDirectory(directory, options2 = {}) {
887
- if (routeDirectories.has(directory)) {
888
- console.warn(`Route directory ${directory} already registered`);
889
- return;
890
- }
891
- routeDirectories.add(directory);
892
- if (initialized) {
893
- await loadRoutesFromDirectory(directory, directory, options2.prefix);
894
- if (routerOptions.watchMode) {
895
- setupWatcherForDirectory(directory, directory, options2.prefix);
896
- }
897
- }
898
- },
899
- /**
900
- * Get route conflicts
901
- */
902
- getRouteConflicts() {
903
- const conflicts = [];
904
- for (const [path5, sources] of routeSources.entries()) {
905
- if (sources.length > 1) {
906
- conflicts.push({ path: path5, sources });
907
- }
908
- }
909
- return conflicts;
910
- }
911
- };
912
650
  }
913
-
914
- // src/config.ts
915
- var config = {};
916
- function setRuntimeConfig(newConfig) {
917
- config = { ...config, ...newConfig };
651
+ function shouldSkipParsing(method) {
652
+ const skipMethods = ["GET", "HEAD", "OPTIONS"];
653
+ return skipMethods.includes(method || "GET");
918
654
  }
919
- function getRoutesDir() {
920
- if (!config.routesDir) {
921
- throw new Error("Routes directory not configured. Make sure server is properly initialized.");
655
+ async function parseBodyByContentType(req, ctx, contentType) {
656
+ if (contentType.includes("application/json")) {
657
+ await parseJsonBody(req, ctx);
658
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
659
+ await parseFormUrlEncodedBody(req, ctx);
660
+ } else if (contentType.includes("text/")) {
661
+ await parseTextBody(req, ctx);
922
662
  }
923
- return config.routesDir;
924
663
  }
925
-
926
- // src/router/create.ts
927
- function getCallerFilePath() {
928
- const originalPrepareStackTrace = Error.prepareStackTrace;
664
+ async function parseJsonBody(req, ctx) {
665
+ const body = await readRequestBody(req);
666
+ if (!body) {
667
+ console.warn("Empty body, skipping JSON parsing");
668
+ return;
669
+ }
670
+ if (body.trim() === "null") {
671
+ console.warn('Body is the string "null"');
672
+ ctx.request.body = null;
673
+ return;
674
+ }
929
675
  try {
930
- Error.prepareStackTrace = (_, stack2) => stack2;
931
- const stack = new Error().stack;
932
- const callerFrame = stack[3];
933
- if (!callerFrame || typeof callerFrame.getFileName !== "function") {
934
- throw new Error("Unable to determine caller file frame");
935
- }
936
- const fileName = callerFrame.getFileName();
937
- if (!fileName) {
938
- throw new Error("Unable to determine caller file name");
676
+ const json = JSON.parse(body);
677
+ ctx.request.body = json;
678
+ } catch (error) {
679
+ ctx.request.body = null;
680
+ setBodyError(ctx, "json_parse_error", "Invalid JSON in request body", error);
681
+ }
682
+ }
683
+ async function parseFormUrlEncodedBody(req, ctx) {
684
+ const body = await readRequestBody(req);
685
+ if (!body) return;
686
+ try {
687
+ ctx.request.body = parseUrlEncodedData(body);
688
+ } catch (error) {
689
+ ctx.request.body = null;
690
+ setBodyError(ctx, "form_parse_error", "Invalid form data in request body", error);
691
+ }
692
+ }
693
+ function parseUrlEncodedData(body) {
694
+ const params = new URLSearchParams(body);
695
+ const formData = {};
696
+ params.forEach((value, key) => {
697
+ if (formData[key] !== void 0) {
698
+ if (Array.isArray(formData[key])) {
699
+ formData[key].push(value);
700
+ } else {
701
+ formData[key] = [formData[key], value];
702
+ }
703
+ } else {
704
+ formData[key] = value;
939
705
  }
940
- return fileName;
941
- } finally {
942
- Error.prepareStackTrace = originalPrepareStackTrace;
706
+ });
707
+ return formData;
708
+ }
709
+ async function parseTextBody(req, ctx) {
710
+ const body = await readRequestBody(req);
711
+ if (body) {
712
+ ctx.request.body = body;
943
713
  }
944
714
  }
945
- function getRoutePath() {
946
- console.log("getRoutePath called");
947
- const callerPath = getCallerFilePath();
948
- const routesDir = getRoutesDir();
949
- const parsedRoute = parseRoutePath(callerPath, routesDir);
950
- console.log(`Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
951
- return parsedRoute.routePath;
715
+ function setBodyError(ctx, type, message, error) {
716
+ ctx.state._bodyError = { type, message, error };
952
717
  }
953
- var createGetRoute = (config2) => {
954
- validateMethodConfig("GET", config2);
955
- const path5 = getRoutePath();
956
- return {
957
- GET: config2,
958
- path: path5
959
- };
960
- };
961
- var createPostRoute = (config2) => {
962
- validateMethodConfig("POST", config2);
963
- const path5 = getRoutePath();
964
- return {
965
- POST: config2,
966
- path: path5
967
- };
968
- };
969
- var createPutRoute = (config2) => {
970
- validateMethodConfig("PUT", config2);
971
- const path5 = getRoutePath();
972
- return {
973
- PUT: config2,
974
- path: path5
975
- };
976
- };
977
- var createDeleteRoute = (config2) => {
978
- validateMethodConfig("DELETE", config2);
979
- const path5 = getRoutePath();
980
- return {
981
- DELETE: config2,
982
- path: path5
983
- };
984
- };
985
- var createPatchRoute = (config2) => {
986
- validateMethodConfig("PATCH", config2);
987
- const path5 = getRoutePath();
988
- return {
989
- PATCH: config2,
990
- path: path5
991
- };
992
- };
993
- var createHeadRoute = (config2) => {
994
- validateMethodConfig("HEAD", config2);
995
- const path5 = getRoutePath();
996
- return {
997
- HEAD: config2,
998
- path: path5
718
+ async function readRequestBody(req) {
719
+ return new Promise((resolve3, reject) => {
720
+ const chunks = [];
721
+ req.on("data", (chunk) => {
722
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
723
+ });
724
+ req.on("end", () => {
725
+ resolve3(Buffer.concat(chunks).toString("utf8"));
726
+ });
727
+ req.on("error", (err) => {
728
+ reject(err);
729
+ });
730
+ });
731
+ }
732
+
733
+ // src/server/request-handler.ts
734
+ function createRequestHandler(serverInstance) {
735
+ return async (req, res) => {
736
+ try {
737
+ const context = await createContext(req, res, {
738
+ parseBody: true
739
+ // Enable automatic body parsing
740
+ });
741
+ const handler = compose(serverInstance.middleware);
742
+ await runWithContext(context, async () => {
743
+ try {
744
+ await handler(context, async () => {
745
+ if (!context.response.sent) {
746
+ await serverInstance.router.handleRequest(context);
747
+ if (!context.response.sent) {
748
+ context.response.status(404).json({
749
+ error: "Not Found",
750
+ message: `Route not found: ${context.request.method} ${context.request.path}`
751
+ });
752
+ }
753
+ }
754
+ });
755
+ } catch (error) {
756
+ console.error("Error processing request:", error);
757
+ if (!context.response.sent) {
758
+ context.response.json(
759
+ {
760
+ error: "Internal Server Error",
761
+ message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
762
+ },
763
+ 500
764
+ );
765
+ }
766
+ }
767
+ });
768
+ } catch (error) {
769
+ console.error("Error creating context:", error);
770
+ res.writeHead(500, { "Content-Type": "application/json" });
771
+ res.end(
772
+ JSON.stringify({
773
+ error: "Internal Server Error",
774
+ message: "Failed to process request"
775
+ })
776
+ );
777
+ }
999
778
  };
1000
- };
1001
- var createOptionsRoute = (config2) => {
1002
- validateMethodConfig("OPTIONS", config2);
1003
- const path5 = getRoutePath();
1004
- return {
1005
- OPTIONS: config2,
1006
- path: path5
779
+ }
780
+
781
+ // src/server/start.ts
782
+ async function prepareCertificates(http2Options) {
783
+ if (!http2Options.enabled) {
784
+ return {};
785
+ }
786
+ const { keyFile, certFile } = http2Options;
787
+ const isDevMode = process.env.NODE_ENV === "development";
788
+ const certificatesMissing = !keyFile || !certFile;
789
+ if (certificatesMissing && isDevMode) {
790
+ const devCerts = await generateDevCertificates();
791
+ return devCerts;
792
+ }
793
+ if (certificatesMissing) {
794
+ throw new Error(
795
+ "HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically."
796
+ );
797
+ }
798
+ return { keyFile, certFile };
799
+ }
800
+ function createServerInstance(isHttp2, certOptions) {
801
+ if (!isHttp2) {
802
+ return http.createServer();
803
+ }
804
+ const http2ServerOptions = {
805
+ allowHTTP1: true
806
+ // Allow fallback to HTTP/1.1
1007
807
  };
1008
- };
1009
- function validateMethodConfig(method, config2) {
1010
- if (!config2.handler || typeof config2.handler !== "function") {
1011
- throw new Error(`Handler for method ${method} must be a function`);
808
+ try {
809
+ if (certOptions.keyFile) {
810
+ http2ServerOptions.key = fs2.readFileSync(certOptions.keyFile);
811
+ }
812
+ if (certOptions.certFile) {
813
+ http2ServerOptions.cert = fs2.readFileSync(certOptions.certFile);
814
+ }
815
+ } catch (err) {
816
+ throw new Error(
817
+ `Failed to read certificate files: ${err instanceof Error ? err.message : String(err)}`
818
+ );
1012
819
  }
1013
- if (config2.middleware && !Array.isArray(config2.middleware)) {
1014
- throw new Error(`Middleware for method ${method} must be an array`);
820
+ return http2.createSecureServer(http2ServerOptions);
821
+ }
822
+ function listenOnPort(server, port, host, isHttp2) {
823
+ return new Promise((resolve3, reject) => {
824
+ server.listen(port, host, () => {
825
+ const protocol = isHttp2 ? "https" : "http";
826
+ const url = `${protocol}://${host}:${port}`;
827
+ console.log(`
828
+ \u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
829
+
830
+ \u26A1 BlaizeJS DEVELOPMENT SERVER HOT AND READY \u26A1
831
+
832
+ \u{1F680} Server: ${url}
833
+ \u{1F525} Hot Reload: Enabled
834
+ \u{1F6E0}\uFE0F Mode: Development
835
+
836
+ Time to build something amazing! \u{1F680}
837
+
838
+ \u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
839
+ `);
840
+ resolve3();
841
+ });
842
+ server.on("error", (err) => {
843
+ console.error("Server error:", err);
844
+ reject(err);
845
+ });
846
+ });
847
+ }
848
+ async function initializePlugins(serverInstance) {
849
+ for (const plugin of serverInstance.plugins) {
850
+ if (typeof plugin.initialize === "function") {
851
+ await plugin.initialize(serverInstance);
852
+ }
1015
853
  }
1016
- if (config2.schema) {
1017
- validateSchema(method, config2.schema);
854
+ }
855
+ async function startServer(serverInstance, serverOptions) {
856
+ if (serverInstance.server) {
857
+ return;
1018
858
  }
1019
- switch (method) {
1020
- case "GET":
1021
- case "HEAD":
1022
- case "DELETE":
1023
- if (config2.schema?.body) {
1024
- console.warn(`Warning: ${method} requests typically don't have request bodies`);
1025
- }
1026
- break;
859
+ try {
860
+ const port = serverOptions.port;
861
+ const host = serverOptions.host;
862
+ await initializePlugins(serverInstance);
863
+ const http2Options = serverOptions.http2 || { enabled: true };
864
+ const isHttp2 = !!http2Options.enabled;
865
+ const certOptions = await prepareCertificates(http2Options);
866
+ if (serverOptions.http2 && certOptions.keyFile && certOptions.certFile) {
867
+ serverOptions.http2.keyFile = certOptions.keyFile;
868
+ serverOptions.http2.certFile = certOptions.certFile;
869
+ }
870
+ const server = createServerInstance(isHttp2, certOptions);
871
+ serverInstance.server = server;
872
+ serverInstance.port = port;
873
+ serverInstance.host = host;
874
+ const requestHandler = createRequestHandler(serverInstance);
875
+ server.on("request", requestHandler);
876
+ await listenOnPort(server, port, host, isHttp2);
877
+ } catch (error) {
878
+ console.error("Failed to start server:", error);
879
+ throw error;
1027
880
  }
1028
881
  }
1029
- function validateSchema(method, schema) {
1030
- const { params, query, body, response } = schema;
1031
- if (params && (!params._def || typeof params.parse !== "function")) {
1032
- throw new Error(`Params schema for ${method} must be a valid Zod schema`);
882
+
883
+ // src/server/stop.ts
884
+ var isShuttingDown = false;
885
+ async function stopServer(serverInstance, options = {}) {
886
+ const server = serverInstance.server;
887
+ const events = serverInstance.events;
888
+ if (isShuttingDown) {
889
+ console.log("\u26A0\uFE0F Shutdown already in progress, ignoring duplicate shutdown request");
890
+ return;
1033
891
  }
1034
- if (query && (!query._def || typeof query.parse !== "function")) {
1035
- throw new Error(`Query schema for ${method} must be a valid Zod schema`);
892
+ if (!server) {
893
+ return;
1036
894
  }
1037
- if (body && (!body._def || typeof body.parse !== "function")) {
1038
- throw new Error(`Body schema for ${method} must be a valid Zod schema`);
895
+ isShuttingDown = true;
896
+ const timeout = options.timeout || 5e3;
897
+ try {
898
+ if (options.onStopping) {
899
+ await options.onStopping();
900
+ }
901
+ events.emit("stopping");
902
+ if (serverInstance.router && typeof serverInstance.router.close === "function") {
903
+ console.log("\u{1F50C} Closing router watchers...");
904
+ try {
905
+ await Promise.race([
906
+ serverInstance.router.close(),
907
+ new Promise(
908
+ (_, reject) => setTimeout(() => reject(new Error("Router close timeout")), 2e3)
909
+ )
910
+ ]);
911
+ console.log("\u2705 Router watchers closed");
912
+ } catch (error) {
913
+ console.error("\u274C Error closing router watchers:", error);
914
+ }
915
+ }
916
+ try {
917
+ await Promise.race([
918
+ serverInstance.pluginManager.onServerStop(serverInstance, server),
919
+ new Promise(
920
+ (_, reject) => setTimeout(() => reject(new Error("Plugin stop timeout")), 2e3)
921
+ )
922
+ ]);
923
+ } catch (error) {
924
+ console.error("\u274C Plugin stop timeout:", error);
925
+ }
926
+ const closePromise = new Promise((resolve3, reject) => {
927
+ server.close((err) => {
928
+ if (err) return reject(err);
929
+ resolve3();
930
+ });
931
+ });
932
+ const timeoutPromise = new Promise((_, reject) => {
933
+ setTimeout(() => {
934
+ reject(new Error("Server shutdown timeout"));
935
+ }, timeout);
936
+ });
937
+ await Promise.race([closePromise, timeoutPromise]);
938
+ try {
939
+ await Promise.race([
940
+ serverInstance.pluginManager.terminatePlugins(serverInstance),
941
+ new Promise(
942
+ (_, reject) => setTimeout(() => reject(new Error("Plugin terminate timeout")), 1e3)
943
+ )
944
+ ]);
945
+ } catch (error) {
946
+ console.error("\u274C Plugin terminate timeout:", error);
947
+ }
948
+ if (options.onStopped) {
949
+ await options.onStopped();
950
+ }
951
+ events.emit("stopped");
952
+ serverInstance.server = null;
953
+ console.log("\u2705 Graceful shutdown completed");
954
+ isShuttingDown = false;
955
+ } catch (error) {
956
+ isShuttingDown = false;
957
+ console.error("\u26A0\uFE0F Shutdown error (forcing exit):", error);
958
+ if (server && typeof server.close === "function") {
959
+ server.close();
960
+ }
961
+ if (process.env.NODE_ENV === "development") {
962
+ console.log("\u{1F504} Forcing exit for development restart...");
963
+ process.exit(0);
964
+ }
965
+ events.emit("error", error);
966
+ throw error;
1039
967
  }
1040
- if (response && (!response._def || typeof response.parse !== "function")) {
1041
- throw new Error(`Response schema for ${method} must be a valid Zod schema`);
968
+ }
969
+ function registerSignalHandlers(stopFn) {
970
+ const isDevelopment = process.env.NODE_ENV === "development";
971
+ if (isDevelopment) {
972
+ const sigintHandler = () => {
973
+ console.log("\u{1F4E4} SIGINT received, forcing exit for development restart...");
974
+ process.exit(0);
975
+ };
976
+ const sigtermHandler = () => {
977
+ console.log("\u{1F4E4} SIGTERM received, forcing exit for development restart...");
978
+ process.exit(0);
979
+ };
980
+ process.on("SIGINT", sigintHandler);
981
+ process.on("SIGTERM", sigtermHandler);
982
+ return {
983
+ unregister: () => {
984
+ process.removeListener("SIGINT", sigintHandler);
985
+ process.removeListener("SIGTERM", sigtermHandler);
986
+ }
987
+ };
988
+ } else {
989
+ const sigintHandler = () => {
990
+ console.log("\u{1F4E4} SIGINT received, starting graceful shutdown...");
991
+ stopFn().catch(console.error);
992
+ };
993
+ const sigtermHandler = () => {
994
+ console.log("\u{1F4E4} SIGTERM received, starting graceful shutdown...");
995
+ stopFn().catch(console.error);
996
+ };
997
+ process.on("SIGINT", sigintHandler);
998
+ process.on("SIGTERM", sigtermHandler);
999
+ return {
1000
+ unregister: () => {
1001
+ process.removeListener("SIGINT", sigintHandler);
1002
+ process.removeListener("SIGTERM", sigtermHandler);
1003
+ }
1004
+ };
1042
1005
  }
1043
1006
  }
1044
1007
 
1045
- // src/server/create.ts
1046
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
1047
- import EventEmitter from "node:events";
1048
-
1049
- // src/server/start.ts
1050
- import * as fs3 from "node:fs";
1051
- import * as http from "node:http";
1052
- import * as http2 from "node:http2";
1008
+ // src/server/validation.ts
1009
+ import { z } from "zod";
1010
+ var middlewareSchema = z.custom(
1011
+ (data) => data !== null && typeof data === "object" && "execute" in data && typeof data.execute === "function",
1012
+ {
1013
+ message: "Expected middleware to have an execute function"
1014
+ }
1015
+ );
1016
+ var pluginSchema = z.custom(
1017
+ (data) => data !== null && typeof data === "object" && "register" in data && typeof data.register === "function",
1018
+ {
1019
+ message: "Expected a valid plugin object with a register method"
1020
+ }
1021
+ );
1022
+ var http2Schema = z.object({
1023
+ enabled: z.boolean().optional().default(true),
1024
+ keyFile: z.string().optional(),
1025
+ certFile: z.string().optional()
1026
+ }).refine(
1027
+ (data) => {
1028
+ if (data.enabled && process.env.NODE_ENV === "production") {
1029
+ return data.keyFile && data.certFile;
1030
+ }
1031
+ return true;
1032
+ },
1033
+ {
1034
+ message: "When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"
1035
+ }
1036
+ );
1037
+ var serverOptionsSchema = z.object({
1038
+ port: z.number().int().positive().optional().default(3e3),
1039
+ host: z.string().optional().default("localhost"),
1040
+ routesDir: z.string().optional().default("./routes"),
1041
+ http2: http2Schema.optional().default({
1042
+ enabled: true
1043
+ }),
1044
+ middleware: z.array(middlewareSchema).optional().default([]),
1045
+ plugins: z.array(pluginSchema).optional().default([])
1046
+ });
1047
+ function validateServerOptions(options) {
1048
+ try {
1049
+ return serverOptionsSchema.parse(options);
1050
+ } catch (error) {
1051
+ if (error instanceof z.ZodError) {
1052
+ const formattedError = error.format();
1053
+ throw new Error(`Invalid server options: ${JSON.stringify(formattedError, null, 2)}`);
1054
+ }
1055
+ throw new Error(`Invalid server options: ${String(error)}`);
1056
+ }
1057
+ }
1053
1058
 
1054
- // src/server/dev-certificate.ts
1055
- import * as fs2 from "node:fs";
1056
- import * as path4 from "node:path";
1057
- import * as selfsigned from "selfsigned";
1058
- async function generateDevCertificates() {
1059
- const certDir = path4.join(process.cwd(), ".blaizejs", "certs");
1060
- const keyPath = path4.join(certDir, "dev.key");
1061
- const certPath = path4.join(certDir, "dev.cert");
1062
- if (fs2.existsSync(keyPath) && fs2.existsSync(certPath)) {
1063
- return {
1064
- keyFile: keyPath,
1065
- certFile: certPath
1066
- };
1059
+ // src/plugins/lifecycle.ts
1060
+ function createPluginLifecycleManager(options = {}) {
1061
+ const { continueOnError = true, debug = false, onError } = options;
1062
+ function log(message, ...args) {
1063
+ if (debug) {
1064
+ console.log(`[PluginLifecycle] ${message}`, ...args);
1065
+ }
1067
1066
  }
1068
- if (!fs2.existsSync(certDir)) {
1069
- fs2.mkdirSync(certDir, { recursive: true });
1067
+ function handleError(plugin, phase, error) {
1068
+ const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
1069
+ if (onError) {
1070
+ onError(plugin, phase, error);
1071
+ } else {
1072
+ console.error(errorMessage, error);
1073
+ }
1074
+ if (!continueOnError) {
1075
+ throw new Error(errorMessage);
1076
+ }
1070
1077
  }
1071
- const attrs = [{ name: "commonName", value: "localhost" }];
1072
- const options = {
1073
- days: 365,
1074
- algorithm: "sha256",
1075
- keySize: 2048,
1076
- extensions: [
1077
- { name: "basicConstraints", cA: true },
1078
- {
1079
- name: "keyUsage",
1080
- keyCertSign: true,
1081
- digitalSignature: true,
1082
- nonRepudiation: true,
1083
- keyEncipherment: true,
1084
- dataEncipherment: true
1085
- },
1086
- {
1087
- name: "extKeyUsage",
1088
- serverAuth: true,
1089
- clientAuth: true
1090
- },
1091
- {
1092
- name: "subjectAltName",
1093
- altNames: [
1094
- { type: 2, value: "localhost" },
1095
- { type: 7, ip: "127.0.0.1" }
1096
- ]
1078
+ return {
1079
+ /**
1080
+ * Initialize all plugins
1081
+ */
1082
+ async initializePlugins(server) {
1083
+ log("Initializing plugins...");
1084
+ for (const plugin of server.plugins) {
1085
+ if (plugin.initialize) {
1086
+ try {
1087
+ log(`Initializing plugin: ${plugin.name}`);
1088
+ await plugin.initialize(server);
1089
+ } catch (error) {
1090
+ handleError(plugin, "initialize", error);
1091
+ }
1092
+ }
1093
+ }
1094
+ log(`Initialized ${server.plugins.length} plugins`);
1095
+ },
1096
+ /**
1097
+ * Terminate all plugins in reverse order
1098
+ */
1099
+ async terminatePlugins(server) {
1100
+ log("Terminating plugins...");
1101
+ const pluginsToTerminate = [...server.plugins].reverse();
1102
+ for (const plugin of pluginsToTerminate) {
1103
+ if (plugin.terminate) {
1104
+ try {
1105
+ log(`Terminating plugin: ${plugin.name}`);
1106
+ await plugin.terminate(server);
1107
+ } catch (error) {
1108
+ handleError(plugin, "terminate", error);
1109
+ }
1110
+ }
1111
+ }
1112
+ log(`Terminated ${pluginsToTerminate.length} plugins`);
1113
+ },
1114
+ /**
1115
+ * Notify plugins that the server has started
1116
+ */
1117
+ async onServerStart(server, httpServer) {
1118
+ log("Notifying plugins of server start...");
1119
+ for (const plugin of server.plugins) {
1120
+ if (plugin.onServerStart) {
1121
+ try {
1122
+ log(`Notifying plugin of server start: ${plugin.name}`);
1123
+ await plugin.onServerStart(httpServer);
1124
+ } catch (error) {
1125
+ handleError(plugin, "onServerStart", error);
1126
+ }
1127
+ }
1128
+ }
1129
+ },
1130
+ /**
1131
+ * Notify plugins that the server is stopping
1132
+ */
1133
+ async onServerStop(server, httpServer) {
1134
+ log("Notifying plugins of server stop...");
1135
+ const pluginsToNotify = [...server.plugins].reverse();
1136
+ for (const plugin of pluginsToNotify) {
1137
+ if (plugin.onServerStop) {
1138
+ try {
1139
+ log(`Notifying plugin of server stop: ${plugin.name}`);
1140
+ await plugin.onServerStop(httpServer);
1141
+ } catch (error) {
1142
+ handleError(plugin, "onServerStop", error);
1143
+ }
1144
+ }
1097
1145
  }
1098
- ]
1099
- };
1100
- const pems = selfsigned.generate(attrs, options);
1101
- fs2.writeFileSync(keyPath, Buffer.from(pems.private, "utf-8"));
1102
- fs2.writeFileSync(certPath, Buffer.from(pems.cert, "utf-8"));
1103
- console.log(`
1104
- \u{1F512} Generated self-signed certificates for development at ${certDir}
1105
- `);
1106
- return {
1107
- keyFile: keyPath,
1108
- certFile: certPath
1146
+ }
1109
1147
  };
1110
1148
  }
1111
1149
 
1112
- // src/context/errors.ts
1113
- var ResponseSentError = class extends Error {
1114
- constructor(message = "\u274C Response has already been sent") {
1115
- super(message);
1116
- this.name = "ResponseSentError";
1150
+ // src/plugins/errors.ts
1151
+ var PluginValidationError = class extends Error {
1152
+ constructor(pluginName, message) {
1153
+ super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
1154
+ this.pluginName = pluginName;
1155
+ this.name = "PluginValidationError";
1117
1156
  }
1118
1157
  };
1119
- var ResponseSentHeaderError = class extends ResponseSentError {
1120
- constructor(message = "Cannot set header after response has been sent") {
1121
- super(message);
1158
+
1159
+ // src/plugins/validation.ts
1160
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
1161
+ "core",
1162
+ "server",
1163
+ "router",
1164
+ "middleware",
1165
+ "context",
1166
+ "blaize",
1167
+ "blaizejs"
1168
+ ]);
1169
+ var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
1170
+ var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
1171
+ function validatePlugin(plugin, options = {}) {
1172
+ const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
1173
+ if (!plugin || typeof plugin !== "object") {
1174
+ throw new PluginValidationError("", "Plugin must be an object");
1122
1175
  }
1123
- };
1124
- var ResponseSentContentError = class extends ResponseSentError {
1125
- constructor(message = "Cannot set content type after response has been sent") {
1126
- super(message);
1176
+ const p = plugin;
1177
+ if (!p.name || typeof p.name !== "string") {
1178
+ throw new PluginValidationError("", "Plugin must have a name (string)");
1127
1179
  }
1128
- };
1129
- var ParseUrlError = class extends ResponseSentError {
1130
- constructor(message = "Invalide URL") {
1131
- super(message);
1180
+ if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
1181
+ throw new PluginValidationError(
1182
+ p.name,
1183
+ "Plugin name must be lowercase letters, numbers, and hyphens only"
1184
+ );
1185
+ }
1186
+ if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
1187
+ throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
1188
+ }
1189
+ if (requireVersion) {
1190
+ if (!p.version || typeof p.version !== "string") {
1191
+ throw new PluginValidationError(p.name, "Plugin must have a version (string)");
1192
+ }
1193
+ if (!VALID_VERSION_PATTERN.test(p.version)) {
1194
+ throw new PluginValidationError(
1195
+ p.name,
1196
+ 'Plugin version must follow semantic versioning (e.g., "1.0.0")'
1197
+ );
1198
+ }
1199
+ }
1200
+ if (!p.register || typeof p.register !== "function") {
1201
+ throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
1202
+ }
1203
+ const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
1204
+ for (const method of lifecycleMethods) {
1205
+ if (p[method] && typeof p[method] !== "function") {
1206
+ throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
1207
+ }
1132
1208
  }
1133
- };
1134
-
1135
- // src/context/store.ts
1136
- import { AsyncLocalStorage } from "node:async_hooks";
1137
- var contextStorage = new AsyncLocalStorage();
1138
- function runWithContext(context, callback) {
1139
- return contextStorage.run(context, callback);
1140
1209
  }
1141
1210
 
1142
- // src/context/create.ts
1143
- var CONTENT_TYPE_HEADER = "Content-Type";
1144
- function parseRequestUrl(req) {
1145
- const originalUrl = req.url || "/";
1146
- const host = req.headers.host || "localhost";
1147
- const protocol = req.socket && req.socket.encrypted ? "https" : "http";
1148
- const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
1211
+ // src/router/discovery/cache.ts
1212
+ import * as crypto from "node:crypto";
1213
+ import * as fs3 from "node:fs/promises";
1214
+ import { createRequire } from "node:module";
1215
+ import * as path3 from "node:path";
1216
+
1217
+ // src/router/discovery/loader.ts
1218
+ async function dynamicImport(filePath) {
1219
+ const cacheBuster = `?t=${Date.now()}`;
1220
+ const importPath = filePath + cacheBuster;
1149
1221
  try {
1150
- const url = new URL(fullUrl);
1151
- const path5 = url.pathname;
1152
- const query = {};
1153
- url.searchParams.forEach((value, key) => {
1154
- if (query[key] !== void 0) {
1155
- if (Array.isArray(query[key])) {
1156
- query[key].push(value);
1157
- } else {
1158
- query[key] = [query[key], value];
1159
- }
1160
- } else {
1161
- query[key] = value;
1162
- }
1163
- });
1164
- return { path: path5, url, query };
1222
+ const module = await import(importPath);
1223
+ console.log(`\u2705 Successfully imported module`);
1224
+ return module;
1165
1225
  } catch (error) {
1166
- console.warn(`Invalid URL: ${fullUrl}`, error);
1167
- throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
1226
+ const errorMessage = error instanceof Error ? error.message : String(error);
1227
+ console.log(`\u26A0\uFE0F Error importing with cache buster, trying original path:`, errorMessage);
1228
+ return import(filePath);
1168
1229
  }
1169
1230
  }
1170
- function isHttp2Request(req) {
1171
- return "stream" in req || "httpVersionMajor" in req && req.httpVersionMajor === 2;
1172
- }
1173
- function getProtocol(req) {
1174
- const encrypted = req.socket && req.socket.encrypted;
1175
- const forwardedProto = req.headers["x-forwarded-proto"];
1176
- if (forwardedProto) {
1177
- if (Array.isArray(forwardedProto)) {
1178
- return forwardedProto[0]?.split(",")[0]?.trim() || "http";
1179
- } else {
1180
- return forwardedProto.split(",")[0]?.trim() || "http";
1231
+ async function loadRouteModule(filePath, basePath) {
1232
+ try {
1233
+ const parsedRoute = parseRoutePath(filePath, basePath);
1234
+ const module = await dynamicImport(filePath);
1235
+ console.log("\u{1F4E6} Module exports:", Object.keys(module));
1236
+ const routes = [];
1237
+ if (module.default && typeof module.default === "object") {
1238
+ const route = {
1239
+ ...module.default,
1240
+ path: parsedRoute.routePath
1241
+ };
1242
+ routes.push(route);
1243
+ }
1244
+ Object.entries(module).forEach(([exportName, exportValue]) => {
1245
+ if (exportName === "default" || !exportValue || typeof exportValue !== "object") {
1246
+ return;
1247
+ }
1248
+ const potentialRoute = exportValue;
1249
+ if (isValidRoute(potentialRoute)) {
1250
+ const route = {
1251
+ ...potentialRoute,
1252
+ // Use the route's own path if it has one, otherwise derive from file
1253
+ path: parsedRoute.routePath
1254
+ };
1255
+ routes.push(route);
1256
+ }
1257
+ });
1258
+ if (routes.length === 0) {
1259
+ console.warn(`Route file ${filePath} does not export any valid route definitions`);
1260
+ return [];
1181
1261
  }
1262
+ console.log(`\u2705 Successfully Loaded ${routes.length} route(s)`);
1263
+ return routes;
1264
+ } catch (error) {
1265
+ console.error(`Failed to load route module ${filePath}:`, error);
1266
+ return [];
1182
1267
  }
1183
- return encrypted ? "https" : "http";
1184
1268
  }
1185
- async function createContext(req, res, options = {}) {
1186
- const { path: path5, url, query } = parseRequestUrl(req);
1187
- const method = req.method || "GET";
1188
- const isHttp2 = isHttp2Request(req);
1189
- const protocol = getProtocol(req);
1190
- const params = {};
1191
- const state = { ...options.initialState || {} };
1192
- const responseState = { sent: false };
1193
- const ctx = {
1194
- request: createRequestObject(req, {
1195
- path: path5,
1196
- url,
1197
- query,
1198
- params,
1199
- method,
1200
- isHttp2,
1201
- protocol
1202
- }),
1203
- response: {},
1204
- state
1205
- };
1206
- ctx.response = createResponseObject(res, responseState, ctx);
1207
- if (options.parseBody) {
1208
- await parseBodyIfNeeded(req, ctx);
1269
+ function isValidRoute(obj) {
1270
+ if (!obj || typeof obj !== "object") {
1271
+ return false;
1209
1272
  }
1210
- return ctx;
1211
- }
1212
- function createRequestObject(req, info) {
1213
- return {
1214
- raw: req,
1215
- ...info,
1216
- header: createRequestHeaderGetter(req),
1217
- headers: createRequestHeadersGetter(req),
1218
- body: void 0
1219
- };
1220
- }
1221
- function createRequestHeaderGetter(req) {
1222
- return (name) => {
1223
- const value = req.headers[name.toLowerCase()];
1224
- if (Array.isArray(value)) {
1225
- return value.join(", ");
1226
- }
1227
- return value || void 0;
1228
- };
1273
+ const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
1274
+ const hasHttpMethod = httpMethods.some(
1275
+ (method) => obj[method] && typeof obj[method] === "object" && obj[method].handler
1276
+ );
1277
+ return hasHttpMethod;
1229
1278
  }
1230
- function createRequestHeadersGetter(req) {
1231
- const headerGetter = createRequestHeaderGetter(req);
1232
- return (names) => {
1233
- if (names && Array.isArray(names) && names.length > 0) {
1234
- return names.reduce((acc, name) => {
1235
- acc[name] = headerGetter(name);
1236
- return acc;
1237
- }, {});
1238
- } else {
1239
- return Object.entries(req.headers).reduce(
1240
- (acc, [key, value]) => {
1241
- acc[key] = Array.isArray(value) ? value.join(", ") : value || void 0;
1242
- return acc;
1243
- },
1244
- {}
1245
- );
1246
- }
1247
- };
1279
+
1280
+ // src/router/discovery/cache.ts
1281
+ var fileRouteCache = /* @__PURE__ */ new Map();
1282
+ async function processChangedFile(filePath, routesDir, updateCache = true) {
1283
+ const stat3 = await fs3.stat(filePath);
1284
+ const lastModified = stat3.mtime.getTime();
1285
+ const cachedEntry = fileRouteCache.get(filePath);
1286
+ if (updateCache && cachedEntry && cachedEntry.timestamp === lastModified) {
1287
+ return cachedEntry.routes;
1288
+ }
1289
+ invalidateModuleCache(filePath);
1290
+ const routes = await loadRouteModule(filePath, routesDir);
1291
+ if (updateCache) {
1292
+ const hash = hashRoutes(routes);
1293
+ fileRouteCache.set(filePath, {
1294
+ routes,
1295
+ timestamp: lastModified,
1296
+ hash
1297
+ });
1298
+ }
1299
+ return routes;
1248
1300
  }
1249
- function createResponseObject(res, responseState, ctx) {
1250
- return {
1251
- raw: res,
1252
- get sent() {
1253
- return responseState.sent;
1254
- },
1255
- status: createStatusSetter(res, responseState, ctx),
1256
- header: createHeaderSetter(res, responseState, ctx),
1257
- headers: createHeadersSetter(res, responseState, ctx),
1258
- type: createContentTypeSetter(res, responseState, ctx),
1259
- json: createJsonResponder(res, responseState),
1260
- text: createTextResponder(res, responseState),
1261
- html: createHtmlResponder(res, responseState),
1262
- redirect: createRedirectResponder(res, responseState),
1263
- stream: createStreamResponder(res, responseState)
1264
- };
1301
+ function hasRouteContentChanged(filePath, newRoutes) {
1302
+ const cachedEntry = fileRouteCache.get(filePath);
1303
+ if (!cachedEntry) {
1304
+ return true;
1305
+ }
1306
+ const newHash = hashRoutes(newRoutes);
1307
+ return cachedEntry.hash !== newHash;
1265
1308
  }
1266
- function createStatusSetter(res, responseState, ctx) {
1267
- return function statusSetter(code) {
1268
- if (responseState.sent) {
1269
- throw new ResponseSentError();
1270
- }
1271
- res.statusCode = code;
1272
- return ctx.response;
1273
- };
1309
+ function clearFileCache(filePath) {
1310
+ if (filePath) {
1311
+ fileRouteCache.delete(filePath);
1312
+ } else {
1313
+ fileRouteCache.clear();
1314
+ }
1274
1315
  }
1275
- function createHeaderSetter(res, responseState, ctx) {
1276
- return function headerSetter(name, value) {
1277
- if (responseState.sent) {
1278
- throw new ResponseSentHeaderError();
1316
+ function hashRoutes(routes) {
1317
+ const routeData = routes.map((route) => ({
1318
+ path: route.path,
1319
+ methods: Object.keys(route).filter((key) => key !== "path").sort().map((method) => {
1320
+ const methodDef = route[method];
1321
+ const handlerString = methodDef?.handler ? methodDef.handler.toString() : null;
1322
+ return {
1323
+ method,
1324
+ // Include handler function string for change detection
1325
+ handler: handlerString,
1326
+ // Include middleware if present
1327
+ middleware: methodDef?.middleware ? methodDef.middleware.length : 0,
1328
+ // Include schema structure (but not full serialization which can be unstable)
1329
+ hasSchema: !!methodDef?.schema,
1330
+ schemaKeys: methodDef?.schema ? Object.keys(methodDef.schema).sort() : []
1331
+ };
1332
+ })
1333
+ }));
1334
+ const dataString = JSON.stringify(routeData);
1335
+ const hash = crypto.createHash("md5").update(dataString).digest("hex");
1336
+ return hash;
1337
+ }
1338
+ function invalidateModuleCache(filePath) {
1339
+ try {
1340
+ const absolutePath = path3.resolve(filePath);
1341
+ if (typeof __require !== "undefined") {
1342
+ delete __require.cache[absolutePath];
1343
+ try {
1344
+ const resolvedPath = __require.resolve(absolutePath);
1345
+ delete __require.cache[resolvedPath];
1346
+ } catch (resolveError) {
1347
+ const errorMessage = resolveError instanceof Error ? resolveError.message : String(resolveError);
1348
+ console.log(`\u26A0\uFE0F Could not resolve path: ${errorMessage}`);
1349
+ }
1350
+ } else {
1351
+ try {
1352
+ const require2 = createRequire(import.meta.url);
1353
+ delete require2.cache[absolutePath];
1354
+ try {
1355
+ const resolvedPath = require2.resolve(absolutePath);
1356
+ delete require2.cache[resolvedPath];
1357
+ } catch {
1358
+ console.log(`\u26A0\uFE0F Could not resolve ESM path`);
1359
+ }
1360
+ } catch {
1361
+ console.log(`\u26A0\uFE0F createRequire not available in pure ESM`);
1362
+ }
1279
1363
  }
1280
- res.setHeader(name, value);
1281
- return ctx.response;
1282
- };
1364
+ } catch (error) {
1365
+ console.log(`\u26A0\uFE0F Error during module cache invalidation for ${filePath}:`, error);
1366
+ }
1283
1367
  }
1284
- function createHeadersSetter(res, responseState, ctx) {
1285
- return function headersSetter(headers) {
1286
- if (responseState.sent) {
1287
- throw new ResponseSentHeaderError();
1368
+
1369
+ // src/router/discovery/parallel.ts
1370
+ import * as os from "node:os";
1371
+
1372
+ // src/router/discovery/finder.ts
1373
+ import * as fs4 from "node:fs/promises";
1374
+ import * as path4 from "node:path";
1375
+ async function findRouteFiles(routesDir, options = {}) {
1376
+ const absoluteDir = path4.isAbsolute(routesDir) ? routesDir : path4.resolve(process.cwd(), routesDir);
1377
+ console.log("Creating router with routes directory:", absoluteDir);
1378
+ try {
1379
+ const stats = await fs4.stat(absoluteDir);
1380
+ if (!stats.isDirectory()) {
1381
+ throw new Error(`Route directory is not a directory: ${absoluteDir}`);
1288
1382
  }
1289
- for (const [name, value] of Object.entries(headers)) {
1290
- res.setHeader(name, value);
1383
+ } catch (error) {
1384
+ if (error.code === "ENOENT") {
1385
+ throw new Error(`Route directory not found: ${absoluteDir}`);
1291
1386
  }
1292
- return ctx.response;
1293
- };
1294
- }
1295
- function createContentTypeSetter(res, responseState, ctx) {
1296
- return function typeSetter(type) {
1297
- if (responseState.sent) {
1298
- throw new ResponseSentContentError();
1387
+ throw error;
1388
+ }
1389
+ const routeFiles = [];
1390
+ const ignore = options.ignore || ["node_modules", ".git"];
1391
+ async function scanDirectory(dir) {
1392
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1393
+ for (const entry of entries) {
1394
+ const fullPath = path4.join(dir, entry.name);
1395
+ if (entry.isDirectory() && ignore.includes(entry.name)) {
1396
+ continue;
1397
+ }
1398
+ if (entry.isDirectory()) {
1399
+ await scanDirectory(fullPath);
1400
+ } else if (isRouteFile(entry.name)) {
1401
+ routeFiles.push(fullPath);
1402
+ }
1299
1403
  }
1300
- res.setHeader(CONTENT_TYPE_HEADER, type);
1301
- return ctx.response;
1302
- };
1404
+ }
1405
+ await scanDirectory(absoluteDir);
1406
+ return routeFiles;
1303
1407
  }
1304
- function createJsonResponder(res, responseState) {
1305
- return function jsonResponder(body, status) {
1306
- if (responseState.sent) {
1307
- throw new ResponseSentError();
1308
- }
1309
- if (status !== void 0) {
1310
- res.statusCode = status;
1408
+ function isRouteFile(filename) {
1409
+ return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
1410
+ }
1411
+
1412
+ // src/router/discovery/parallel.ts
1413
+ async function processFilesInParallel(filePaths, processor, concurrency = Math.max(1, Math.floor(os.cpus().length / 2))) {
1414
+ const chunks = chunkArray(filePaths, concurrency);
1415
+ const results = [];
1416
+ for (const chunk of chunks) {
1417
+ const chunkResults = await Promise.allSettled(chunk.map((filePath) => processor(filePath)));
1418
+ const successfulResults = chunkResults.filter((result) => result.status === "fulfilled").map((result) => result.value);
1419
+ results.push(...successfulResults);
1420
+ }
1421
+ return results;
1422
+ }
1423
+ async function loadInitialRoutesParallel(routesDir) {
1424
+ const files = await findRouteFiles(routesDir);
1425
+ const routeArrays = await processFilesInParallel(
1426
+ files,
1427
+ (filePath) => processChangedFile(filePath, routesDir)
1428
+ );
1429
+ return routeArrays.flat();
1430
+ }
1431
+ function chunkArray(array, chunkSize) {
1432
+ const chunks = [];
1433
+ for (let i = 0; i < array.length; i += chunkSize) {
1434
+ chunks.push(array.slice(i, i + chunkSize));
1435
+ }
1436
+ return chunks;
1437
+ }
1438
+
1439
+ // src/router/discovery/profiler.ts
1440
+ var profilerState = {
1441
+ fileChanges: 0,
1442
+ totalReloadTime: 0,
1443
+ averageReloadTime: 0,
1444
+ slowReloads: []
1445
+ };
1446
+ function trackReloadPerformance(filePath, startTime) {
1447
+ const duration = Date.now() - startTime;
1448
+ profilerState.fileChanges++;
1449
+ profilerState.totalReloadTime += duration;
1450
+ profilerState.averageReloadTime = profilerState.totalReloadTime / profilerState.fileChanges;
1451
+ if (duration > 100) {
1452
+ profilerState.slowReloads.push({ file: filePath, time: duration });
1453
+ if (profilerState.slowReloads.length > 10) {
1454
+ profilerState.slowReloads.shift();
1455
+ }
1456
+ }
1457
+ if (process.env.NODE_ENV === "development") {
1458
+ const emoji = duration < 50 ? "\u26A1" : duration < 100 ? "\u{1F504}" : "\u{1F40C}";
1459
+ console.log(`${emoji} Route reload: ${filePath} (${duration}ms)`);
1460
+ }
1461
+ }
1462
+ function withPerformanceTracking(fn, filePath) {
1463
+ console.log(`Tracking performance for: ${filePath}`);
1464
+ return async (...args) => {
1465
+ const startTime = Date.now();
1466
+ try {
1467
+ const result = await fn(...args);
1468
+ trackReloadPerformance(filePath, startTime);
1469
+ return result;
1470
+ } catch (error) {
1471
+ trackReloadPerformance(filePath, startTime);
1472
+ throw error;
1311
1473
  }
1312
- res.setHeader(CONTENT_TYPE_HEADER, "application/json");
1313
- res.end(JSON.stringify(body));
1314
- responseState.sent = true;
1315
1474
  };
1316
1475
  }
1317
- function createTextResponder(res, responseState) {
1318
- return function textResponder(body, status) {
1319
- if (responseState.sent) {
1320
- throw new ResponseSentError();
1476
+
1477
+ // src/router/discovery/watchers.ts
1478
+ import * as path5 from "node:path";
1479
+ import { watch } from "chokidar";
1480
+ function watchRoutes(routesDir, options = {}) {
1481
+ const debounceMs = options.debounceMs || 16;
1482
+ const debouncedCallbacks = /* @__PURE__ */ new Map();
1483
+ function createDebouncedCallback(fn, filePath) {
1484
+ return (...args) => {
1485
+ const existingTimeout = debouncedCallbacks.get(filePath);
1486
+ if (existingTimeout) {
1487
+ clearTimeout(existingTimeout);
1488
+ }
1489
+ const timeoutId = setTimeout(() => {
1490
+ fn(...args);
1491
+ debouncedCallbacks.delete(filePath);
1492
+ }, debounceMs);
1493
+ debouncedCallbacks.set(filePath, timeoutId);
1494
+ };
1495
+ }
1496
+ const routesByPath = /* @__PURE__ */ new Map();
1497
+ async function loadInitialRoutes() {
1498
+ try {
1499
+ const files = await findRouteFiles(routesDir, {
1500
+ ignore: options.ignore
1501
+ });
1502
+ for (const filePath of files) {
1503
+ await loadAndNotify(filePath);
1504
+ }
1505
+ } catch (error) {
1506
+ handleError(error);
1321
1507
  }
1322
- if (status !== void 0) {
1323
- res.statusCode = status;
1508
+ }
1509
+ async function loadAndNotify(filePath) {
1510
+ try {
1511
+ const existingRoutes = routesByPath.get(filePath);
1512
+ const newRoutes = await processChangedFile(filePath, routesDir, false);
1513
+ if (!newRoutes || newRoutes.length === 0) {
1514
+ return;
1515
+ }
1516
+ if (existingRoutes && !hasRouteContentChanged(filePath, newRoutes)) {
1517
+ return;
1518
+ }
1519
+ await processChangedFile(filePath, routesDir, true);
1520
+ const normalizedPath = path5.normalize(filePath);
1521
+ if (existingRoutes) {
1522
+ routesByPath.set(filePath, newRoutes);
1523
+ if (options.onRouteChanged) {
1524
+ options.onRouteChanged(normalizedPath, newRoutes);
1525
+ }
1526
+ } else {
1527
+ routesByPath.set(filePath, newRoutes);
1528
+ if (options.onRouteAdded) {
1529
+ options.onRouteAdded(normalizedPath, newRoutes);
1530
+ }
1531
+ }
1532
+ } catch (error) {
1533
+ console.log(`\u26A0\uFE0F Error processing file ${filePath}:`, error);
1534
+ handleError(error);
1324
1535
  }
1325
- res.setHeader(CONTENT_TYPE_HEADER, "text/plain");
1326
- res.end(body);
1327
- responseState.sent = true;
1328
- };
1329
- }
1330
- function createHtmlResponder(res, responseState) {
1331
- return function htmlResponder(body, status) {
1332
- if (responseState.sent) {
1333
- throw new ResponseSentError();
1536
+ }
1537
+ function handleRemoved(filePath) {
1538
+ const normalizedPath = path5.normalize(filePath);
1539
+ const routes = routesByPath.get(normalizedPath);
1540
+ if (routes && routes.length > 0 && options.onRouteRemoved) {
1541
+ options.onRouteRemoved(normalizedPath, routes);
1334
1542
  }
1335
- if (status !== void 0) {
1336
- res.statusCode = status;
1543
+ routesByPath.delete(normalizedPath);
1544
+ }
1545
+ function handleError(error) {
1546
+ if (options.onError && error instanceof Error) {
1547
+ options.onError(error);
1548
+ } else {
1549
+ console.error("\u26A0\uFE0F Route watcher error:", error);
1337
1550
  }
1338
- res.setHeader(CONTENT_TYPE_HEADER, "text/html");
1339
- res.end(body);
1340
- responseState.sent = true;
1551
+ }
1552
+ const watcher = watch(routesDir, {
1553
+ // Much faster response times
1554
+ awaitWriteFinish: {
1555
+ stabilityThreshold: 50,
1556
+ // Reduced from 300ms
1557
+ pollInterval: 10
1558
+ // Reduced from 100ms
1559
+ },
1560
+ // Performance optimizations
1561
+ usePolling: false,
1562
+ atomic: true,
1563
+ followSymlinks: false,
1564
+ depth: 10,
1565
+ // More aggressive ignoring
1566
+ ignored: [
1567
+ /(^|[/\\])\../,
1568
+ /node_modules/,
1569
+ /\.git/,
1570
+ /\.DS_Store/,
1571
+ /Thumbs\.db/,
1572
+ /\.(test|spec)\.(ts|js)$/,
1573
+ /\.d\.ts$/,
1574
+ /\.map$/,
1575
+ /~$/,
1576
+ ...options.ignore || []
1577
+ ]
1578
+ });
1579
+ watcher.on("add", (filePath) => {
1580
+ const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
1581
+ debouncedLoad(filePath);
1582
+ }).on("change", (filePath) => {
1583
+ const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
1584
+ debouncedLoad(filePath);
1585
+ }).on("unlink", (filePath) => {
1586
+ const debouncedRemove = createDebouncedCallback(handleRemoved, filePath);
1587
+ debouncedRemove(filePath);
1588
+ }).on("error", handleError);
1589
+ loadInitialRoutes().catch(handleError);
1590
+ return {
1591
+ close: () => {
1592
+ debouncedCallbacks.forEach((timeout) => clearTimeout(timeout));
1593
+ debouncedCallbacks.clear();
1594
+ return watcher.close();
1595
+ },
1596
+ getRoutes: () => {
1597
+ const allRoutes = [];
1598
+ for (const routes of routesByPath.values()) {
1599
+ allRoutes.push(...routes);
1600
+ }
1601
+ return allRoutes;
1602
+ },
1603
+ getRoutesByFile: () => new Map(routesByPath)
1341
1604
  };
1342
1605
  }
1343
- function createRedirectResponder(res, responseState) {
1344
- return function redirectResponder(url, status = 302) {
1345
- if (responseState.sent) {
1346
- throw new ResponseSentError();
1347
- }
1348
- res.statusCode = status;
1349
- res.setHeader("Location", url);
1350
- res.end();
1351
- responseState.sent = true;
1606
+
1607
+ // src/router/handlers/error.ts
1608
+ function handleRouteError(ctx, error, options = {}) {
1609
+ if (options.log) {
1610
+ console.error("Route error:", error);
1611
+ }
1612
+ const status = getErrorStatus(error);
1613
+ const response = {
1614
+ error: getErrorType(error),
1615
+ message: getErrorMessage(error)
1352
1616
  };
1353
- }
1354
- function createStreamResponder(res, responseState) {
1355
- return function streamResponder(readable, options = {}) {
1356
- if (responseState.sent) {
1357
- throw new ResponseSentError();
1617
+ if (options.detailed) {
1618
+ if (error instanceof Error) {
1619
+ response.stack = error.stack;
1358
1620
  }
1359
- if (options.status !== void 0) {
1360
- res.statusCode = options.status;
1621
+ if (error && typeof error === "object" && "details" in error && error.details) {
1622
+ response.details = error.details;
1361
1623
  }
1362
- if (options.contentType) {
1363
- res.setHeader(CONTENT_TYPE_HEADER, options.contentType);
1624
+ }
1625
+ ctx.response.status(status).json(response);
1626
+ }
1627
+ function getErrorStatus(error) {
1628
+ if (error && typeof error === "object") {
1629
+ if ("status" in error && typeof error.status === "number") {
1630
+ return error.status;
1364
1631
  }
1365
- if (options.headers) {
1366
- for (const [name, value] of Object.entries(options.headers)) {
1367
- res.setHeader(name, value);
1368
- }
1632
+ if ("statusCode" in error && typeof error.statusCode === "number") {
1633
+ return error.statusCode;
1369
1634
  }
1370
- readable.pipe(res);
1371
- readable.on("end", () => {
1372
- responseState.sent = true;
1373
- });
1374
- readable.on("error", (err) => {
1375
- console.error("Stream error:", err);
1376
- if (!responseState.sent) {
1377
- res.statusCode = 500;
1378
- res.end("Stream error");
1379
- responseState.sent = true;
1380
- }
1381
- });
1382
- };
1635
+ if ("code" in error && typeof error.code === "string") {
1636
+ return getStatusFromCode(error.code);
1637
+ }
1638
+ }
1639
+ return 500;
1640
+ }
1641
+ function getStatusFromCode(code) {
1642
+ switch (code) {
1643
+ case "NOT_FOUND":
1644
+ return 404;
1645
+ case "UNAUTHORIZED":
1646
+ return 401;
1647
+ case "FORBIDDEN":
1648
+ return 403;
1649
+ case "BAD_REQUEST":
1650
+ return 400;
1651
+ case "CONFLICT":
1652
+ return 409;
1653
+ default:
1654
+ return 500;
1655
+ }
1383
1656
  }
1384
- async function parseBodyIfNeeded(req, ctx) {
1385
- if (shouldSkipParsing(req.method)) {
1386
- return;
1657
+ function getErrorType(error) {
1658
+ if (error && typeof error === "object") {
1659
+ if ("type" in error && typeof error.type === "string") {
1660
+ return error.type;
1661
+ }
1662
+ if ("name" in error && typeof error.name === "string") {
1663
+ return error.name;
1664
+ }
1665
+ if (error instanceof Error) {
1666
+ return error.constructor.name;
1667
+ }
1387
1668
  }
1388
- const contentType = req.headers["content-type"] || "";
1389
- const contentLength = parseInt(req.headers["content-length"] || "0", 10);
1390
- if (contentLength === 0 || contentLength > 1048576) {
1391
- return;
1669
+ return "Error";
1670
+ }
1671
+ function getErrorMessage(error) {
1672
+ if (error instanceof Error) {
1673
+ return error.message;
1392
1674
  }
1393
- try {
1394
- await parseBodyByContentType(req, ctx, contentType);
1395
- } catch (error) {
1396
- setBodyError(ctx, "body_read_error", "Error reading request body", error);
1675
+ if (error && typeof error === "object") {
1676
+ if ("message" in error && typeof error.message === "string") {
1677
+ return error.message;
1678
+ }
1397
1679
  }
1680
+ return String(error);
1398
1681
  }
1399
- function shouldSkipParsing(method) {
1400
- const skipMethods = ["GET", "HEAD", "OPTIONS"];
1401
- return skipMethods.includes(method || "GET");
1402
- }
1403
- async function parseBodyByContentType(req, ctx, contentType) {
1404
- if (contentType.includes("application/json")) {
1405
- await parseJsonBody(req, ctx);
1406
- } else if (contentType.includes("application/x-www-form-urlencoded")) {
1407
- await parseFormUrlEncodedBody(req, ctx);
1408
- } else if (contentType.includes("text/")) {
1409
- await parseTextBody(req, ctx);
1682
+
1683
+ // src/router/validation/body.ts
1684
+ import { z as z2 } from "zod";
1685
+ function validateBody(body, schema) {
1686
+ if (schema instanceof z2.ZodObject) {
1687
+ return schema.strict().parse(body);
1410
1688
  }
1689
+ return schema.parse(body);
1411
1690
  }
1412
- async function parseJsonBody(req, ctx) {
1413
- const body = await readRequestBody(req);
1414
- if (!body) {
1415
- console.warn("Empty body, skipping JSON parsing");
1416
- return;
1417
- }
1418
- if (body.trim() === "null") {
1419
- console.warn('Body is the string "null"');
1420
- ctx.request.body = null;
1421
- return;
1691
+
1692
+ // src/router/validation/params.ts
1693
+ import { z as z3 } from "zod";
1694
+ function validateParams(params, schema) {
1695
+ if (schema instanceof z3.ZodObject) {
1696
+ return schema.strict().parse(params);
1422
1697
  }
1423
- try {
1424
- const json = JSON.parse(body);
1425
- ctx.request.body = json;
1426
- } catch (error) {
1427
- ctx.request.body = null;
1428
- setBodyError(ctx, "json_parse_error", "Invalid JSON in request body", error);
1698
+ return schema.parse(params);
1699
+ }
1700
+
1701
+ // src/router/validation/query.ts
1702
+ import { z as z4 } from "zod";
1703
+ function validateQuery(query, schema) {
1704
+ if (schema instanceof z4.ZodObject) {
1705
+ return schema.strict().parse(query);
1429
1706
  }
1707
+ return schema.parse(query);
1430
1708
  }
1431
- async function parseFormUrlEncodedBody(req, ctx) {
1432
- const body = await readRequestBody(req);
1433
- if (!body) return;
1434
- try {
1435
- ctx.request.body = parseUrlEncodedData(body);
1436
- } catch (error) {
1437
- ctx.request.body = null;
1438
- setBodyError(ctx, "form_parse_error", "Invalid form data in request body", error);
1709
+
1710
+ // src/router/validation/response.ts
1711
+ import { z as z5 } from "zod";
1712
+ function validateResponse(response, schema) {
1713
+ if (schema instanceof z5.ZodObject) {
1714
+ return schema.strict().parse(response);
1439
1715
  }
1716
+ return schema.parse(response);
1440
1717
  }
1441
- function parseUrlEncodedData(body) {
1442
- const params = new URLSearchParams(body);
1443
- const formData = {};
1444
- params.forEach((value, key) => {
1445
- if (formData[key] !== void 0) {
1446
- if (Array.isArray(formData[key])) {
1447
- formData[key].push(value);
1448
- } else {
1449
- formData[key] = [formData[key], value];
1718
+
1719
+ // src/router/validation/schema.ts
1720
+ function createRequestValidator(schema, debug = false) {
1721
+ const middlewareFn = async (ctx, next) => {
1722
+ const errors = {};
1723
+ if (schema.params && ctx.request.params) {
1724
+ try {
1725
+ ctx.request.params = validateParams(ctx.request.params, schema.params);
1726
+ } catch (error) {
1727
+ errors.params = formatValidationError(error);
1450
1728
  }
1451
- } else {
1452
- formData[key] = value;
1453
1729
  }
1454
- });
1455
- return formData;
1456
- }
1457
- async function parseTextBody(req, ctx) {
1458
- const body = await readRequestBody(req);
1459
- if (body) {
1460
- ctx.request.body = body;
1461
- }
1730
+ if (schema.query && ctx.request.query) {
1731
+ try {
1732
+ ctx.request.query = validateQuery(ctx.request.query, schema.query);
1733
+ } catch (error) {
1734
+ errors.query = formatValidationError(error);
1735
+ }
1736
+ }
1737
+ if (schema.body) {
1738
+ try {
1739
+ ctx.request.body = validateBody(ctx.request.body, schema.body);
1740
+ } catch (error) {
1741
+ errors.body = formatValidationError(error);
1742
+ }
1743
+ }
1744
+ if (Object.keys(errors).length > 0) {
1745
+ ctx.response.status(400).json({
1746
+ error: "Validation Error",
1747
+ details: errors
1748
+ });
1749
+ return;
1750
+ }
1751
+ await next();
1752
+ };
1753
+ return {
1754
+ name: "RequestValidator",
1755
+ execute: middlewareFn,
1756
+ debug
1757
+ };
1462
1758
  }
1463
- function setBodyError(ctx, type, message, error) {
1464
- ctx.state._bodyError = { type, message, error };
1759
+ function createResponseValidator(responseSchema, debug = false) {
1760
+ const middlewareFn = async (ctx, next) => {
1761
+ const originalJson = ctx.response.json;
1762
+ ctx.response.json = (body, status) => {
1763
+ try {
1764
+ const validatedBody = validateResponse(body, responseSchema);
1765
+ ctx.response.json = originalJson;
1766
+ return originalJson.call(ctx.response, validatedBody, status);
1767
+ } catch (error) {
1768
+ ctx.response.json = originalJson;
1769
+ console.error("Response validation error:", error);
1770
+ ctx.response.status(500).json({
1771
+ error: "Internal Server Error",
1772
+ message: "Response validation failed"
1773
+ });
1774
+ return ctx.response;
1775
+ }
1776
+ };
1777
+ await next();
1778
+ };
1779
+ return {
1780
+ name: "ResponseValidator",
1781
+ execute: middlewareFn,
1782
+ debug
1783
+ };
1465
1784
  }
1466
- async function readRequestBody(req) {
1467
- return new Promise((resolve2, reject) => {
1468
- const chunks = [];
1469
- req.on("data", (chunk) => {
1470
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1471
- });
1472
- req.on("end", () => {
1473
- resolve2(Buffer.concat(chunks).toString("utf8"));
1474
- });
1475
- req.on("error", (err) => {
1476
- reject(err);
1477
- });
1478
- });
1785
+ function formatValidationError(error) {
1786
+ if (error && typeof error === "object" && "format" in error && typeof error.format === "function") {
1787
+ return error.format();
1788
+ }
1789
+ return error instanceof Error ? error.message : String(error);
1479
1790
  }
1480
1791
 
1481
- // src/server/request-handler.ts
1482
- function createRequestHandler(serverInstance) {
1483
- return async (req, res) => {
1484
- try {
1485
- const context = await createContext(req, res, {
1486
- parseBody: true
1487
- // Enable automatic body parsing
1488
- });
1489
- const handler = compose(serverInstance.middleware);
1490
- await runWithContext(context, async () => {
1491
- try {
1492
- await handler(context, async () => {
1493
- if (!context.response.sent) {
1494
- await serverInstance.router.handleRequest(context);
1495
- if (!context.response.sent) {
1496
- context.response.status(404).json({
1497
- error: "Not Found",
1498
- message: `Route not found: ${context.request.method} ${context.request.path}`
1499
- });
1500
- }
1501
- }
1502
- });
1503
- } catch (error) {
1504
- console.error("Error processing request:", error);
1505
- if (!context.response.sent) {
1506
- context.response.json(
1507
- {
1508
- error: "Internal Server Error",
1509
- message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
1510
- },
1511
- 500
1512
- );
1513
- }
1514
- }
1515
- });
1516
- } catch (error) {
1517
- console.error("Error creating context:", error);
1518
- res.writeHead(500, { "Content-Type": "application/json" });
1519
- res.end(
1520
- JSON.stringify({
1521
- error: "Internal Server Error",
1522
- message: "Failed to process request"
1523
- })
1524
- );
1792
+ // src/router/handlers/executor.ts
1793
+ async function executeHandler(ctx, routeOptions, params) {
1794
+ const middleware = [...routeOptions.middleware || []];
1795
+ if (routeOptions.schema) {
1796
+ if (routeOptions.schema.params || routeOptions.schema.query || routeOptions.schema.body) {
1797
+ middleware.unshift(createRequestValidator(routeOptions.schema));
1798
+ }
1799
+ if (routeOptions.schema.response) {
1800
+ middleware.push(createResponseValidator(routeOptions.schema.response));
1801
+ }
1802
+ }
1803
+ const handler = compose([...middleware]);
1804
+ await handler(ctx, async () => {
1805
+ const result = await routeOptions.handler(ctx, params);
1806
+ if (!ctx.response.sent && result !== void 0) {
1807
+ ctx.response.json(result);
1525
1808
  }
1526
- };
1809
+ });
1527
1810
  }
1528
1811
 
1529
- // src/server/start.ts
1530
- async function prepareCertificates(http2Options) {
1531
- if (!http2Options.enabled) {
1812
+ // src/router/matching/params.ts
1813
+ function extractParams(path6, pattern, paramNames) {
1814
+ const match = pattern.exec(path6);
1815
+ if (!match) {
1532
1816
  return {};
1533
1817
  }
1534
- const { keyFile, certFile } = http2Options;
1535
- const isDevMode = process.env.NODE_ENV === "development";
1536
- const certificatesMissing = !keyFile || !certFile;
1537
- if (certificatesMissing && isDevMode) {
1538
- const devCerts = await generateDevCertificates();
1539
- return devCerts;
1540
- }
1541
- if (certificatesMissing) {
1542
- throw new Error(
1543
- "HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically."
1544
- );
1818
+ const params = {};
1819
+ for (let i = 0; i < paramNames.length; i++) {
1820
+ params[paramNames[i]] = match[i + 1] || "";
1545
1821
  }
1546
- return { keyFile, certFile };
1822
+ return params;
1547
1823
  }
1548
- function createServerInstance(isHttp2, certOptions) {
1549
- if (!isHttp2) {
1550
- return http.createServer();
1824
+ function compilePathPattern(path6) {
1825
+ const paramNames = [];
1826
+ if (path6 === "/") {
1827
+ return {
1828
+ pattern: /^\/$/,
1829
+ paramNames: []
1830
+ };
1551
1831
  }
1552
- const http2ServerOptions = {
1553
- allowHTTP1: true
1554
- // Allow fallback to HTTP/1.1
1832
+ let patternString = path6.replace(/([.+*?^$(){}|\\])/g, "\\$1");
1833
+ patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
1834
+ paramNames.push(paramName);
1835
+ return "/([^/]+)";
1836
+ }).replace(/\/\[([^\]]+)\]/g, (_, paramName) => {
1837
+ paramNames.push(paramName);
1838
+ return "/([^/]+)";
1839
+ });
1840
+ patternString = `${patternString}(?:/)?`;
1841
+ const pattern = new RegExp(`^${patternString}$`);
1842
+ return {
1843
+ pattern,
1844
+ paramNames
1555
1845
  };
1556
- try {
1557
- if (certOptions.keyFile) {
1558
- http2ServerOptions.key = fs3.readFileSync(certOptions.keyFile);
1559
- }
1560
- if (certOptions.certFile) {
1561
- http2ServerOptions.cert = fs3.readFileSync(certOptions.certFile);
1562
- }
1563
- } catch (err) {
1564
- throw new Error(
1565
- `Failed to read certificate files: ${err instanceof Error ? err.message : String(err)}`
1566
- );
1567
- }
1568
- return http2.createSecureServer(http2ServerOptions);
1569
1846
  }
1570
- function listenOnPort(server, port, host, isHttp2) {
1571
- return new Promise((resolve2, reject) => {
1572
- server.listen(port, host, () => {
1573
- const protocol = isHttp2 ? "https" : "http";
1574
- const url = `${protocol}://${host}:${port}`;
1575
- console.log(`
1576
- \u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
1577
1847
 
1578
- \u26A1 BlaizeJS DEVELOPMENT SERVER HOT AND READY \u26A1
1579
-
1580
- \u{1F680} Server: ${url}
1581
- \u{1F525} Hot Reload: Enabled
1582
- \u{1F6E0}\uFE0F Mode: Development
1583
-
1584
- Time to build something amazing! \u{1F680}
1848
+ // src/router/matching/matcher.ts
1849
+ function createMatcher() {
1850
+ const routes = [];
1851
+ return {
1852
+ /**
1853
+ * Add a route to the matcher
1854
+ */
1855
+ add(path6, method, routeOptions) {
1856
+ const { pattern, paramNames } = compilePathPattern(path6);
1857
+ const newRoute = {
1858
+ path: path6,
1859
+ method,
1860
+ pattern,
1861
+ paramNames,
1862
+ routeOptions
1863
+ };
1864
+ const insertIndex = routes.findIndex((route) => paramNames.length < route.paramNames.length);
1865
+ if (insertIndex === -1) {
1866
+ routes.push(newRoute);
1867
+ } else {
1868
+ routes.splice(insertIndex, 0, newRoute);
1869
+ }
1870
+ },
1871
+ /**
1872
+ * Remove a route from the matcher by path
1873
+ */
1874
+ remove(path6) {
1875
+ for (let i = routes.length - 1; i >= 0; i--) {
1876
+ if (routes[i].path === path6) {
1877
+ routes.splice(i, 1);
1878
+ }
1879
+ }
1880
+ },
1881
+ /**
1882
+ * Clear all routes from the matcher
1883
+ */
1884
+ clear() {
1885
+ routes.length = 0;
1886
+ },
1887
+ /**
1888
+ * Match a URL path to a route
1889
+ */
1890
+ match(path6, method) {
1891
+ const pathname = path6.split("?")[0];
1892
+ if (!pathname) return null;
1893
+ for (const route of routes) {
1894
+ if (route.method !== method) continue;
1895
+ const match = route.pattern.exec(pathname);
1896
+ if (match) {
1897
+ const params = extractParams(path6, route.pattern, route.paramNames);
1898
+ return {
1899
+ route: route.routeOptions,
1900
+ params
1901
+ };
1902
+ }
1903
+ }
1904
+ const matchingPath = routes.find(
1905
+ (route) => route.method !== method && route.pattern.test(path6)
1906
+ );
1907
+ if (matchingPath) {
1908
+ return {
1909
+ route: null,
1910
+ params: {},
1911
+ methodNotAllowed: true,
1912
+ allowedMethods: routes.filter((route) => route.pattern.test(path6)).map((route) => route.method)
1913
+ };
1914
+ }
1915
+ return null;
1916
+ },
1917
+ /**
1918
+ * Get all registered routes
1919
+ */
1920
+ getRoutes() {
1921
+ return routes.map((route) => ({
1922
+ path: route.path,
1923
+ method: route.method
1924
+ }));
1925
+ },
1926
+ /**
1927
+ * Find routes matching a specific path
1928
+ */
1929
+ findRoutes(path6) {
1930
+ return routes.filter((route) => route.pattern.test(path6)).map((route) => ({
1931
+ path: route.path,
1932
+ method: route.method,
1933
+ params: extractParams(path6, route.pattern, route.paramNames)
1934
+ }));
1935
+ }
1936
+ };
1937
+ }
1585
1938
 
1586
- \u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
1587
- `);
1588
- resolve2();
1589
- });
1590
- server.on("error", (err) => {
1591
- console.error("Server error:", err);
1592
- reject(err);
1593
- });
1939
+ // src/router/registry/fast-registry.ts
1940
+ function createRouteRegistry() {
1941
+ return {
1942
+ routesByPath: /* @__PURE__ */ new Map(),
1943
+ routesByFile: /* @__PURE__ */ new Map(),
1944
+ pathToFile: /* @__PURE__ */ new Map()
1945
+ };
1946
+ }
1947
+ function updateRoutesFromFile(registry, filePath, newRoutes) {
1948
+ console.log(`Updating routes from file: ${filePath}`);
1949
+ const oldPaths = registry.routesByFile.get(filePath) || /* @__PURE__ */ new Set();
1950
+ const newPaths = new Set(newRoutes.map((r) => r.path));
1951
+ const added = newRoutes.filter((r) => !oldPaths.has(r.path));
1952
+ const removed = Array.from(oldPaths).filter((p) => !newPaths.has(p));
1953
+ const potentiallyChanged = newRoutes.filter((r) => oldPaths.has(r.path));
1954
+ const changed = potentiallyChanged.filter((route) => {
1955
+ const existingRoute = registry.routesByPath.get(route.path);
1956
+ return !existingRoute || !routesEqual(existingRoute, route);
1594
1957
  });
1958
+ applyRouteUpdates(registry, filePath, { added, removed, changed });
1959
+ return { added, removed, changed };
1595
1960
  }
1596
- async function initializePlugins(serverInstance) {
1597
- for (const plugin of serverInstance.plugins) {
1598
- if (typeof plugin.initialize === "function") {
1599
- await plugin.initialize(serverInstance);
1600
- }
1601
- }
1961
+ function getAllRoutesFromRegistry(registry) {
1962
+ return Array.from(registry.routesByPath.values());
1602
1963
  }
1603
- async function startServer(serverInstance, serverOptions) {
1604
- if (serverInstance.server) {
1605
- return;
1606
- }
1607
- try {
1608
- const port = serverOptions.port;
1609
- const host = serverOptions.host;
1610
- await initializePlugins(serverInstance);
1611
- const http2Options = serverOptions.http2 || { enabled: true };
1612
- const isHttp2 = !!http2Options.enabled;
1613
- const certOptions = await prepareCertificates(http2Options);
1614
- if (serverOptions.http2 && certOptions.keyFile && certOptions.certFile) {
1615
- serverOptions.http2.keyFile = certOptions.keyFile;
1616
- serverOptions.http2.certFile = certOptions.certFile;
1617
- }
1618
- const server = createServerInstance(isHttp2, certOptions);
1619
- serverInstance.server = server;
1620
- serverInstance.port = port;
1621
- serverInstance.host = host;
1622
- const requestHandler = createRequestHandler(serverInstance);
1623
- server.on("request", requestHandler);
1624
- await listenOnPort(server, port, host, isHttp2);
1625
- } catch (error) {
1626
- console.error("Failed to start server:", error);
1627
- throw error;
1628
- }
1964
+ function applyRouteUpdates(registry, filePath, updates) {
1965
+ const { added, removed, changed } = updates;
1966
+ removed.forEach((path6) => {
1967
+ registry.routesByPath.delete(path6);
1968
+ registry.pathToFile.delete(path6);
1969
+ });
1970
+ [...added, ...changed].forEach((route) => {
1971
+ registry.routesByPath.set(route.path, route);
1972
+ registry.pathToFile.set(route.path, filePath);
1973
+ });
1974
+ const allPathsForFile = /* @__PURE__ */ new Set([
1975
+ ...added.map((r) => r.path),
1976
+ ...changed.map((r) => r.path),
1977
+ ...Array.from(registry.routesByFile.get(filePath) || []).filter((p) => !removed.includes(p))
1978
+ ]);
1979
+ if (allPathsForFile.size > 0) {
1980
+ registry.routesByFile.set(filePath, allPathsForFile);
1981
+ } else {
1982
+ registry.routesByFile.delete(filePath);
1983
+ }
1984
+ }
1985
+ function routesEqual(route1, route2) {
1986
+ if (route1.path !== route2.path) return false;
1987
+ const methods1 = Object.keys(route1).filter((k) => k !== "path").sort();
1988
+ const methods2 = Object.keys(route2).filter((k) => k !== "path").sort();
1989
+ if (methods1.length !== methods2.length) return false;
1990
+ return methods1.every((method) => {
1991
+ const handler1 = route1[method];
1992
+ const handler2 = route2[method];
1993
+ return typeof handler1 === typeof handler2;
1994
+ });
1629
1995
  }
1630
1996
 
1631
- // src/server/stop.ts
1632
- async function stopServer(serverInstance, options = {}) {
1633
- const server = serverInstance.server;
1634
- const events = serverInstance.events;
1635
- if (!server) {
1636
- return;
1637
- }
1638
- const timeout = options.timeout || 3e4;
1639
- try {
1640
- if (options.onStopping) {
1641
- await options.onStopping();
1642
- }
1643
- events.emit("stopping");
1644
- await serverInstance.pluginManager.onServerStop(serverInstance, server);
1645
- const timeoutPromise = new Promise((_, reject) => {
1646
- setTimeout(() => {
1647
- reject(new Error("Server shutdown timed out waiting for requests to complete"));
1648
- }, timeout);
1649
- });
1650
- const closePromise = new Promise((resolve2, reject) => {
1651
- server.close((err) => {
1652
- if (err) {
1653
- return reject(err);
1654
- }
1655
- resolve2();
1656
- });
1657
- });
1658
- await Promise.race([closePromise, timeoutPromise]);
1659
- await serverInstance.pluginManager.terminatePlugins(serverInstance);
1660
- if (options.onStopped) {
1661
- await options.onStopped();
1662
- }
1663
- events.emit("stopped");
1664
- serverInstance.server = null;
1665
- } catch (error) {
1666
- events.emit("error", error);
1667
- throw error;
1997
+ // src/router/utils/matching-helpers.ts
1998
+ function addRouteToMatcher(route, matcher) {
1999
+ Object.entries(route).forEach(([method, methodOptions]) => {
2000
+ if (method === "path" || !methodOptions) return;
2001
+ matcher.add(route.path, method, methodOptions);
2002
+ });
2003
+ }
2004
+ function removeRouteFromMatcher(path6, matcher) {
2005
+ if ("remove" in matcher && typeof matcher.remove === "function") {
2006
+ matcher.remove(path6);
2007
+ } else {
2008
+ console.warn("Matcher does not support selective removal, consider adding remove() method");
1668
2009
  }
1669
2010
  }
1670
- function registerSignalHandlers(stopFn) {
1671
- const sigintHandler = () => stopFn().catch(console.error);
1672
- const sigtermHandler = () => stopFn().catch(console.error);
1673
- process.on("SIGINT", sigintHandler);
1674
- process.on("SIGTERM", sigtermHandler);
1675
- return {
1676
- unregister: () => {
1677
- process.removeListener("SIGINT", sigintHandler);
1678
- process.removeListener("SIGTERM", sigtermHandler);
1679
- }
1680
- };
2011
+ function updateRouteInMatcher(route, matcher) {
2012
+ removeRouteFromMatcher(route.path, matcher);
2013
+ addRouteToMatcher(route, matcher);
1681
2014
  }
1682
2015
 
1683
- // src/server/validation.ts
1684
- import { z as z5 } from "zod";
1685
- var middlewareSchema = z5.custom(
1686
- (data) => data !== null && typeof data === "object" && "execute" in data && typeof data.execute === "function",
1687
- {
1688
- message: "Expected middleware to have an execute function"
1689
- }
1690
- );
1691
- var pluginSchema = z5.custom(
1692
- (data) => data !== null && typeof data === "object" && "register" in data && typeof data.register === "function",
1693
- {
1694
- message: "Expected a valid plugin object with a register method"
2016
+ // src/router/router.ts
2017
+ var DEFAULT_ROUTER_OPTIONS = {
2018
+ routesDir: "./routes",
2019
+ basePath: "/",
2020
+ watchMode: process.env.NODE_ENV === "development"
2021
+ };
2022
+ function createRouter(options) {
2023
+ const routerOptions = {
2024
+ ...DEFAULT_ROUTER_OPTIONS,
2025
+ ...options
2026
+ };
2027
+ if (options.basePath && !options.basePath.startsWith("/")) {
2028
+ console.warn("Base path does nothing");
1695
2029
  }
1696
- );
1697
- var http2Schema = z5.object({
1698
- enabled: z5.boolean().optional().default(true),
1699
- keyFile: z5.string().optional(),
1700
- certFile: z5.string().optional()
1701
- }).refine(
1702
- (data) => {
1703
- if (data.enabled && process.env.NODE_ENV === "production") {
1704
- return data.keyFile && data.certFile;
2030
+ const registry = createRouteRegistry();
2031
+ const matcher = createMatcher();
2032
+ let initialized = false;
2033
+ let initializationPromise = null;
2034
+ let _watchers = null;
2035
+ const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
2036
+ function applyMatcherChanges(changes) {
2037
+ console.log("\n\u{1F527} APPLYING MATCHER CHANGES:");
2038
+ console.log(` Adding ${changes.added.length} routes`);
2039
+ console.log(` Removing ${changes.removed.length} routes`);
2040
+ console.log(` Updating ${changes.changed.length} routes`);
2041
+ changes.removed.forEach((routePath) => {
2042
+ console.log(` \u2796 Removing: ${routePath}`);
2043
+ removeRouteFromMatcher(routePath, matcher);
2044
+ });
2045
+ changes.added.forEach((route) => {
2046
+ const methods = Object.keys(route).filter((key) => key !== "path");
2047
+ console.log(` \u2795 Adding: ${route.path} [${methods.join(", ")}]`);
2048
+ addRouteToMatcher(route, matcher);
2049
+ });
2050
+ changes.changed.forEach((route) => {
2051
+ const methods = Object.keys(route).filter((key) => key !== "path");
2052
+ console.log(` \u{1F504} Updating: ${route.path} [${methods.join(", ")}]`);
2053
+ updateRouteInMatcher(route, matcher);
2054
+ });
2055
+ console.log("\u2705 Matcher changes applied\n");
2056
+ }
2057
+ function addRoutesWithSource(routes, source) {
2058
+ try {
2059
+ const changes = updateRoutesFromFile(registry, source, routes);
2060
+ applyMatcherChanges(changes);
2061
+ return changes;
2062
+ } catch (error) {
2063
+ console.error(`\u26A0\uFE0F Route conflicts from ${source}:`, error);
2064
+ throw error;
1705
2065
  }
1706
- return true;
1707
- },
1708
- {
1709
- message: "When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"
1710
2066
  }
1711
- );
1712
- var serverOptionsSchema = z5.object({
1713
- port: z5.number().int().positive().optional().default(3e3),
1714
- host: z5.string().optional().default("localhost"),
1715
- routesDir: z5.string().optional().default("./routes"),
1716
- http2: http2Schema.optional().default({
1717
- enabled: true
1718
- }),
1719
- middleware: z5.array(middlewareSchema).optional().default([]),
1720
- plugins: z5.array(pluginSchema).optional().default([])
1721
- });
1722
- function validateServerOptions(options) {
1723
- try {
1724
- return serverOptionsSchema.parse(options);
1725
- } catch (error) {
1726
- if (error instanceof z5.ZodError) {
1727
- const formattedError = error.format();
1728
- throw new Error(`Invalid server options: ${JSON.stringify(formattedError, null, 2)}`);
2067
+ async function loadRoutesFromDirectory(directory, source, prefix) {
2068
+ try {
2069
+ const discoveredRoutes = await loadInitialRoutesParallel(directory);
2070
+ const finalRoutes = discoveredRoutes.map(
2071
+ (route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
2072
+ );
2073
+ const changes = addRoutesWithSource(finalRoutes, source);
2074
+ console.log(
2075
+ `Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""} (${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed)`
2076
+ );
2077
+ } catch (error) {
2078
+ console.error(`\u26A0\uFE0F Failed to load routes from ${source}:`, error);
2079
+ throw error;
1729
2080
  }
1730
- throw new Error(`Invalid server options: ${String(error)}`);
1731
2081
  }
1732
- }
1733
-
1734
- // src/plugins/lifecycle.ts
1735
- function createPluginLifecycleManager(options = {}) {
1736
- const { continueOnError = true, debug = false, onError } = options;
1737
- function log(message, ...args) {
1738
- if (debug) {
1739
- console.log(`[PluginLifecycle] ${message}`, ...args);
2082
+ async function initialize() {
2083
+ if (initialized || initializationPromise) {
2084
+ return initializationPromise;
1740
2085
  }
2086
+ initializationPromise = (async () => {
2087
+ try {
2088
+ await Promise.all(
2089
+ Array.from(routeDirectories).map(
2090
+ (directory) => loadRoutesFromDirectory(directory, directory)
2091
+ )
2092
+ );
2093
+ if (routerOptions.watchMode) {
2094
+ setupOptimizedWatching();
2095
+ }
2096
+ initialized = true;
2097
+ } catch (error) {
2098
+ console.error("\u26A0\uFE0F Failed to initialize router:", error);
2099
+ throw error;
2100
+ }
2101
+ })();
2102
+ return initializationPromise;
1741
2103
  }
1742
- function handleError(plugin, phase, error) {
1743
- const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
1744
- if (onError) {
1745
- onError(plugin, phase, error);
1746
- } else {
1747
- console.error(errorMessage, error);
2104
+ function setupOptimizedWatching() {
2105
+ if (!_watchers) {
2106
+ _watchers = /* @__PURE__ */ new Map();
1748
2107
  }
1749
- if (!continueOnError) {
1750
- throw new Error(errorMessage);
2108
+ for (const directory of routeDirectories) {
2109
+ if (!_watchers.has(directory)) {
2110
+ const watcher = watchRoutes(directory, {
2111
+ debounceMs: 16,
2112
+ // ~60fps debouncing
2113
+ ignore: ["node_modules", ".git"],
2114
+ onRouteAdded: (filepath, addedRoutes) => {
2115
+ try {
2116
+ const changes = updateRoutesFromFile(registry, filepath, addedRoutes);
2117
+ applyMatcherChanges(changes);
2118
+ } catch (error) {
2119
+ console.error(`Error adding routes from ${directory}:`, error);
2120
+ }
2121
+ },
2122
+ onRouteChanged: withPerformanceTracking(
2123
+ async (filepath, changedRoutes) => {
2124
+ try {
2125
+ console.log(`Processing changes for ${filepath}`);
2126
+ const changes = updateRoutesFromFile(registry, filepath, changedRoutes);
2127
+ console.log(
2128
+ `Changes detected: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
2129
+ );
2130
+ applyMatcherChanges(changes);
2131
+ console.log(
2132
+ `Route changes applied: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
2133
+ );
2134
+ } catch (error) {
2135
+ console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
2136
+ }
2137
+ },
2138
+ directory
2139
+ ),
2140
+ onRouteRemoved: (filePath, removedRoutes) => {
2141
+ console.log(`File removed: ${filePath} with ${removedRoutes.length} routes`);
2142
+ try {
2143
+ removedRoutes.forEach((route) => {
2144
+ removeRouteFromMatcher(route.path, matcher);
2145
+ });
2146
+ clearFileCache(filePath);
2147
+ } catch (error) {
2148
+ console.error(`\u26A0\uFE0F Error removing routes from ${filePath}:`, error);
2149
+ }
2150
+ },
2151
+ onError: (error) => {
2152
+ console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
2153
+ }
2154
+ });
2155
+ _watchers.set(directory, watcher);
2156
+ }
2157
+ }
2158
+ }
2159
+ function setupWatcherForNewDirectory(directory, prefix) {
2160
+ if (!_watchers) {
2161
+ _watchers = /* @__PURE__ */ new Map();
1751
2162
  }
2163
+ const watcher = watchRoutes(directory, {
2164
+ debounceMs: 16,
2165
+ ignore: ["node_modules", ".git"],
2166
+ onRouteAdded: (filePath, addedRoutes) => {
2167
+ try {
2168
+ const finalRoutes = addedRoutes.map(
2169
+ (route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
2170
+ );
2171
+ const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
2172
+ applyMatcherChanges(changes);
2173
+ } catch (error) {
2174
+ console.error(`\u26A0\uFE0F Error adding routes from ${directory}:`, error);
2175
+ }
2176
+ },
2177
+ onRouteChanged: withPerformanceTracking(async (filePath, changedRoutes) => {
2178
+ try {
2179
+ const finalRoutes = changedRoutes.map(
2180
+ (route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
2181
+ );
2182
+ const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
2183
+ applyMatcherChanges(changes);
2184
+ } catch (error) {
2185
+ console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
2186
+ }
2187
+ }, directory),
2188
+ onRouteRemoved: (filePath, removedRoutes) => {
2189
+ try {
2190
+ removedRoutes.forEach((route) => {
2191
+ const finalPath = prefix ? `${prefix}${route.path}` : route.path;
2192
+ removeRouteFromMatcher(finalPath, matcher);
2193
+ });
2194
+ clearFileCache(filePath);
2195
+ } catch (error) {
2196
+ console.error(`Error removing routes from ${filePath}:`, error);
2197
+ }
2198
+ },
2199
+ onError: (error) => {
2200
+ console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
2201
+ }
2202
+ });
2203
+ _watchers.set(directory, watcher);
2204
+ return watcher;
1752
2205
  }
2206
+ initialize().catch((error) => {
2207
+ console.error("\u26A0\uFE0F Failed to initialize router on creation:", error);
2208
+ });
1753
2209
  return {
1754
2210
  /**
1755
- * Initialize all plugins
2211
+ * Handle an incoming request
1756
2212
  */
1757
- async initializePlugins(server) {
1758
- log("Initializing plugins...");
1759
- for (const plugin of server.plugins) {
1760
- if (plugin.initialize) {
1761
- try {
1762
- log(`Initializing plugin: ${plugin.name}`);
1763
- await plugin.initialize(server);
1764
- } catch (error) {
1765
- handleError(plugin, "initialize", error);
1766
- }
2213
+ async handleRequest(ctx) {
2214
+ if (!initialized) {
2215
+ console.log("\u{1F504} Router not initialized, initializing...");
2216
+ await initialize();
2217
+ }
2218
+ const { method, path: path6 } = ctx.request;
2219
+ console.log(`
2220
+ \u{1F4E5} Handling request: ${method} ${path6}`);
2221
+ const match = matcher.match(path6, method);
2222
+ if (!match) {
2223
+ console.log(`\u274C No match found for: ${method} ${path6}`);
2224
+ ctx.response.status(404).json({ error: "Not Found" });
2225
+ return;
2226
+ }
2227
+ console.log(`\u2705 Route matched: ${method} ${path6}`);
2228
+ console.log(` Params: ${JSON.stringify(match.params)}`);
2229
+ if (match.methodNotAllowed) {
2230
+ ctx.response.status(405).json({
2231
+ error: "\u274C Method Not Allowed",
2232
+ allowed: match.allowedMethods
2233
+ });
2234
+ if (match.allowedMethods && match.allowedMethods.length > 0) {
2235
+ ctx.response.header("Allow", match.allowedMethods.join(", "));
1767
2236
  }
2237
+ return;
2238
+ }
2239
+ ctx.request.params = match.params;
2240
+ try {
2241
+ await executeHandler(ctx, match.route, match.params);
2242
+ } catch (error) {
2243
+ handleRouteError(ctx, error, {
2244
+ detailed: process.env.NODE_ENV !== "production",
2245
+ log: true
2246
+ });
1768
2247
  }
1769
- log(`Initialized ${server.plugins.length} plugins`);
1770
2248
  },
1771
2249
  /**
1772
- * Terminate all plugins in reverse order
2250
+ * Get all registered routes (using optimized registry)
1773
2251
  */
1774
- async terminatePlugins(server) {
1775
- log("Terminating plugins...");
1776
- const pluginsToTerminate = [...server.plugins].reverse();
1777
- for (const plugin of pluginsToTerminate) {
1778
- if (plugin.terminate) {
1779
- try {
1780
- log(`Terminating plugin: ${plugin.name}`);
1781
- await plugin.terminate(server);
1782
- } catch (error) {
1783
- handleError(plugin, "terminate", error);
1784
- }
1785
- }
1786
- }
1787
- log(`Terminated ${pluginsToTerminate.length} plugins`);
2252
+ getRoutes() {
2253
+ return getAllRoutesFromRegistry(registry);
1788
2254
  },
1789
2255
  /**
1790
- * Notify plugins that the server has started
2256
+ * Add a route programmatically
1791
2257
  */
1792
- async onServerStart(server, httpServer) {
1793
- log("Notifying plugins of server start...");
1794
- for (const plugin of server.plugins) {
1795
- if (plugin.onServerStart) {
1796
- try {
1797
- log(`Notifying plugin of server start: ${plugin.name}`);
1798
- await plugin.onServerStart(httpServer);
1799
- } catch (error) {
1800
- handleError(plugin, "onServerStart", error);
1801
- }
2258
+ addRoute(route) {
2259
+ const changes = updateRoutesFromFile(registry, "programmatic", [route]);
2260
+ applyMatcherChanges(changes);
2261
+ },
2262
+ /**
2263
+ * Add multiple routes programmatically with batch processing
2264
+ */
2265
+ addRoutes(routes) {
2266
+ const changes = updateRoutesFromFile(registry, "programmatic", routes);
2267
+ applyMatcherChanges(changes);
2268
+ return changes;
2269
+ },
2270
+ /**
2271
+ * Add a route directory (for plugins) with optimized loading
2272
+ */
2273
+ async addRouteDirectory(directory, options2 = {}) {
2274
+ if (routeDirectories.has(directory)) {
2275
+ console.warn(`Route directory ${directory} already registered`);
2276
+ return;
2277
+ }
2278
+ routeDirectories.add(directory);
2279
+ if (initialized) {
2280
+ await loadRoutesFromDirectory(directory, directory, options2.prefix);
2281
+ if (routerOptions.watchMode) {
2282
+ setupWatcherForNewDirectory(directory, options2.prefix);
1802
2283
  }
1803
2284
  }
1804
2285
  },
1805
2286
  /**
1806
- * Notify plugins that the server is stopping
2287
+ * Get route conflicts (using registry)
1807
2288
  */
1808
- async onServerStop(server, httpServer) {
1809
- log("Notifying plugins of server stop...");
1810
- const pluginsToNotify = [...server.plugins].reverse();
1811
- for (const plugin of pluginsToNotify) {
1812
- if (plugin.onServerStop) {
1813
- try {
1814
- log(`Notifying plugin of server stop: ${plugin.name}`);
1815
- await plugin.onServerStop(httpServer);
1816
- } catch (error) {
1817
- handleError(plugin, "onServerStop", error);
1818
- }
2289
+ getRouteConflicts() {
2290
+ const conflicts = [];
2291
+ return conflicts;
2292
+ },
2293
+ /**
2294
+ * Close watchers and cleanup (useful for testing)
2295
+ */
2296
+ async close() {
2297
+ if (_watchers) {
2298
+ for (const watcher of _watchers.values()) {
2299
+ await watcher.close();
1819
2300
  }
2301
+ _watchers.clear();
1820
2302
  }
1821
2303
  }
1822
2304
  };
1823
2305
  }
1824
2306
 
1825
- // src/plugins/errors.ts
1826
- var PluginValidationError = class extends Error {
1827
- constructor(pluginName, message) {
1828
- super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
1829
- this.pluginName = pluginName;
1830
- this.name = "PluginValidationError";
1831
- }
1832
- };
1833
-
1834
- // src/plugins/validation.ts
1835
- var RESERVED_NAMES = /* @__PURE__ */ new Set([
1836
- "core",
1837
- "server",
1838
- "router",
1839
- "middleware",
1840
- "context",
1841
- "blaize",
1842
- "blaizejs"
1843
- ]);
1844
- var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
1845
- var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
1846
- function validatePlugin(plugin, options = {}) {
1847
- const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
1848
- if (!plugin || typeof plugin !== "object") {
1849
- throw new PluginValidationError("", "Plugin must be an object");
1850
- }
1851
- const p = plugin;
1852
- if (!p.name || typeof p.name !== "string") {
1853
- throw new PluginValidationError("", "Plugin must have a name (string)");
1854
- }
1855
- if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
1856
- throw new PluginValidationError(
1857
- p.name,
1858
- "Plugin name must be lowercase letters, numbers, and hyphens only"
1859
- );
1860
- }
1861
- if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
1862
- throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
1863
- }
1864
- if (requireVersion) {
1865
- if (!p.version || typeof p.version !== "string") {
1866
- throw new PluginValidationError(p.name, "Plugin must have a version (string)");
1867
- }
1868
- if (!VALID_VERSION_PATTERN.test(p.version)) {
1869
- throw new PluginValidationError(
1870
- p.name,
1871
- 'Plugin version must follow semantic versioning (e.g., "1.0.0")'
1872
- );
1873
- }
1874
- }
1875
- if (!p.register || typeof p.register !== "function") {
1876
- throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
1877
- }
1878
- const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
1879
- for (const method of lifecycleMethods) {
1880
- if (p[method] && typeof p[method] !== "function") {
1881
- throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
1882
- }
1883
- }
1884
- }
1885
-
1886
2307
  // src/server/create.ts
1887
2308
  var DEFAULT_OPTIONS = {
1888
2309
  port: 3e3,