blaizejs 0.2.0 → 0.2.2

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.cjs CHANGED
@@ -1,6 +1,6 @@
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.2
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
@@ -59,31 +59,6 @@ __export(index_exports, {
59
59
  });
60
60
  module.exports = __toCommonJS(index_exports);
61
61
 
62
- // src/middleware/create.ts
63
- function create(handlerOrOptions) {
64
- if (typeof handlerOrOptions === "function") {
65
- return {
66
- name: "anonymous",
67
- // Default name for function middleware
68
- execute: handlerOrOptions,
69
- debug: false
70
- };
71
- }
72
- const { name = "anonymous", handler, skip, debug = false } = handlerOrOptions;
73
- const middleware = {
74
- name,
75
- execute: handler,
76
- debug
77
- };
78
- if (skip !== void 0) {
79
- return {
80
- ...middleware,
81
- skip
82
- };
83
- }
84
- return middleware;
85
- }
86
-
87
62
  // src/middleware/execute.ts
88
63
  function execute(middleware, ctx, next) {
89
64
  if (!middleware) {
@@ -131,6 +106,31 @@ function compose(middlewareStack) {
131
106
  };
132
107
  }
133
108
 
109
+ // src/middleware/create.ts
110
+ function create(handlerOrOptions) {
111
+ if (typeof handlerOrOptions === "function") {
112
+ return {
113
+ name: "anonymous",
114
+ // Default name for function middleware
115
+ execute: handlerOrOptions,
116
+ debug: false
117
+ };
118
+ }
119
+ const { name = "anonymous", handler, skip, debug = false } = handlerOrOptions;
120
+ const middleware = {
121
+ name,
122
+ execute: handler,
123
+ debug
124
+ };
125
+ if (skip !== void 0) {
126
+ return {
127
+ ...middleware,
128
+ skip
129
+ };
130
+ }
131
+ return middleware;
132
+ }
133
+
134
134
  // src/plugins/create.ts
135
135
  function create2(name, version, setup, defaultOptions = {}) {
136
136
  if (!name || typeof name !== "string") {
@@ -159,48 +159,20 @@ function create2(name, version, setup, defaultOptions = {}) {
159
159
  };
160
160
  }
161
161
 
162
- // src/router/discovery/finder.ts
163
- var fs = __toESM(require("fs/promises"), 1);
164
- var path = __toESM(require("path"), 1);
165
- async function findRouteFiles(routesDir, options = {}) {
166
- const absoluteDir = path.isAbsolute(routesDir) ? routesDir : path.resolve(process.cwd(), routesDir);
167
- console.log("Creating router with routes directory:", absoluteDir);
168
- try {
169
- const stats = await fs.stat(absoluteDir);
170
- if (!stats.isDirectory()) {
171
- throw new Error(`Route directory is not a directory: ${absoluteDir}`);
172
- }
173
- } catch (error) {
174
- if (error.code === "ENOENT") {
175
- throw new Error(`Route directory not found: ${absoluteDir}`);
176
- }
177
- throw error;
178
- }
179
- const routeFiles = [];
180
- const ignore = options.ignore || ["node_modules", ".git"];
181
- async function scanDirectory(dir) {
182
- const entries = await fs.readdir(dir, { withFileTypes: true });
183
- for (const entry of entries) {
184
- const fullPath = path.join(dir, entry.name);
185
- if (entry.isDirectory() && ignore.includes(entry.name)) {
186
- continue;
187
- }
188
- if (entry.isDirectory()) {
189
- await scanDirectory(fullPath);
190
- } else if (isRouteFile(entry.name)) {
191
- routeFiles.push(fullPath);
192
- }
193
- }
194
- }
195
- await scanDirectory(absoluteDir);
196
- return routeFiles;
162
+ // src/config.ts
163
+ var config = {};
164
+ function setRuntimeConfig(newConfig) {
165
+ config = { ...config, ...newConfig };
197
166
  }
198
- function isRouteFile(filename) {
199
- return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
167
+ function getRoutesDir() {
168
+ if (!config.routesDir) {
169
+ throw new Error("Routes directory not configured. Make sure server is properly initialized.");
170
+ }
171
+ return config.routesDir;
200
172
  }
201
173
 
202
174
  // src/router/discovery/parser.ts
203
- var path2 = __toESM(require("path"), 1);
175
+ var path = __toESM(require("path"), 1);
204
176
  function parseRoutePath(filePath, basePath) {
205
177
  if (filePath.startsWith("file://")) {
206
178
  filePath = filePath.replace("file://", "");
@@ -220,7 +192,7 @@ function parseRoutePath(filePath, basePath) {
220
192
  relativePath = relativePath.substring(1);
221
193
  }
222
194
  } else {
223
- relativePath = path2.relative(forwardSlashBasePath, forwardSlashFilePath).replace(/\\/g, "/");
195
+ relativePath = path.relative(forwardSlashBasePath, forwardSlashFilePath).replace(/\\/g, "/");
224
196
  }
225
197
  relativePath = relativePath.replace(/\.[^.]+$/, "");
226
198
  const segments = relativePath.split("/").filter(Boolean);
@@ -244,1697 +216,1725 @@ function parseRoutePath(filePath, basePath) {
244
216
  };
245
217
  }
246
218
 
247
- // src/router/discovery/loader.ts
248
- async function dynamicImport(filePath) {
249
- return import(filePath);
250
- }
251
- async function loadRouteModule(filePath, basePath) {
219
+ // src/router/create.ts
220
+ function getCallerFilePath() {
221
+ const originalPrepareStackTrace = Error.prepareStackTrace;
252
222
  try {
253
- const parsedRoute = parseRoutePath(filePath, basePath);
254
- console.log("parsedRoute:", parsedRoute);
255
- const module2 = await dynamicImport(filePath);
256
- console.log("Module exports:", Object.keys(module2));
257
- const routes = [];
258
- if (module2.default && typeof module2.default === "object") {
259
- console.log("Found default export:", module2.default);
260
- const route = {
261
- ...module2.default,
262
- path: parsedRoute.routePath
263
- };
264
- routes.push(route);
223
+ Error.prepareStackTrace = (_, stack2) => stack2;
224
+ const stack = new Error().stack;
225
+ const callerFrame = stack[3];
226
+ if (!callerFrame || typeof callerFrame.getFileName !== "function") {
227
+ throw new Error("Unable to determine caller file frame");
265
228
  }
266
- Object.entries(module2).forEach(([exportName, exportValue]) => {
267
- if (exportName === "default" || !exportValue || typeof exportValue !== "object") {
268
- return;
269
- }
270
- const potentialRoute = exportValue;
271
- if (isValidRoute(potentialRoute)) {
272
- console.log(`Found named route export: ${exportName}`, potentialRoute);
273
- const route = {
274
- ...potentialRoute,
275
- // Use the route's own path if it has one, otherwise derive from file
276
- path: parsedRoute.routePath
277
- };
278
- routes.push(route);
279
- }
280
- });
281
- if (routes.length === 0) {
282
- console.warn(`Route file ${filePath} does not export any valid route definitions`);
283
- return [];
229
+ const fileName = callerFrame.getFileName();
230
+ if (!fileName) {
231
+ throw new Error("Unable to determine caller file name");
284
232
  }
285
- console.log(`Loaded ${routes.length} route(s) from ${filePath}`);
286
- return routes;
287
- } catch (error) {
288
- console.error(`Failed to load route module ${filePath}:`, error);
289
- return [];
233
+ return fileName;
234
+ } finally {
235
+ Error.prepareStackTrace = originalPrepareStackTrace;
290
236
  }
291
237
  }
292
- function isValidRoute(obj) {
293
- if (!obj || typeof obj !== "object") {
294
- return false;
295
- }
296
- const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
297
- const hasHttpMethod = httpMethods.some(
298
- (method) => obj[method] && typeof obj[method] === "object" && obj[method].handler
299
- );
300
- return hasHttpMethod;
238
+ function getRoutePath() {
239
+ console.log("getRoutePath called");
240
+ const callerPath = getCallerFilePath();
241
+ const routesDir = getRoutesDir();
242
+ const parsedRoute = parseRoutePath(callerPath, routesDir);
243
+ console.log(`Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
244
+ return parsedRoute.routePath;
301
245
  }
302
-
303
- // src/router/discovery/index.ts
304
- async function findRoutes(routesDir, options = {}) {
305
- const routeFiles = await findRouteFiles(routesDir, {
306
- ignore: options.ignore
307
- });
308
- const routes = [];
309
- for (const filePath of routeFiles) {
310
- const moduleRoutes = await loadRouteModule(filePath, routesDir);
311
- if (moduleRoutes.length > 0) {
312
- routes.push(...moduleRoutes);
313
- }
246
+ var createGetRoute = (config2) => {
247
+ validateMethodConfig("GET", config2);
248
+ const path5 = getRoutePath();
249
+ return {
250
+ GET: config2,
251
+ path: path5
252
+ };
253
+ };
254
+ var createPostRoute = (config2) => {
255
+ validateMethodConfig("POST", config2);
256
+ const path5 = getRoutePath();
257
+ return {
258
+ POST: config2,
259
+ path: path5
260
+ };
261
+ };
262
+ var createPutRoute = (config2) => {
263
+ validateMethodConfig("PUT", config2);
264
+ const path5 = getRoutePath();
265
+ return {
266
+ PUT: config2,
267
+ path: path5
268
+ };
269
+ };
270
+ var createDeleteRoute = (config2) => {
271
+ validateMethodConfig("DELETE", config2);
272
+ const path5 = getRoutePath();
273
+ return {
274
+ DELETE: config2,
275
+ path: path5
276
+ };
277
+ };
278
+ var createPatchRoute = (config2) => {
279
+ validateMethodConfig("PATCH", config2);
280
+ const path5 = getRoutePath();
281
+ return {
282
+ PATCH: config2,
283
+ path: path5
284
+ };
285
+ };
286
+ var createHeadRoute = (config2) => {
287
+ validateMethodConfig("HEAD", config2);
288
+ const path5 = getRoutePath();
289
+ return {
290
+ HEAD: config2,
291
+ path: path5
292
+ };
293
+ };
294
+ var createOptionsRoute = (config2) => {
295
+ validateMethodConfig("OPTIONS", config2);
296
+ const path5 = getRoutePath();
297
+ return {
298
+ OPTIONS: config2,
299
+ path: path5
300
+ };
301
+ };
302
+ function validateMethodConfig(method, config2) {
303
+ if (!config2.handler || typeof config2.handler !== "function") {
304
+ throw new Error(`Handler for method ${method} must be a function`);
314
305
  }
315
- return routes;
316
- }
317
-
318
- // src/router/discovery/watchers.ts
319
- var path3 = __toESM(require("path"), 1);
320
- var import_chokidar = require("chokidar");
321
- function watchRoutes(routesDir, options = {}) {
322
- const routesByPath = /* @__PURE__ */ new Map();
323
- async function loadInitialRoutes() {
324
- try {
325
- const files = await findRouteFiles(routesDir, {
326
- ignore: options.ignore
327
- });
328
- for (const filePath of files) {
329
- await loadAndNotify(filePath);
330
- }
331
- } catch (error) {
332
- handleError(error);
333
- }
306
+ if (config2.middleware && !Array.isArray(config2.middleware)) {
307
+ throw new Error(`Middleware for method ${method} must be an array`);
334
308
  }
335
- async function loadAndNotify(filePath) {
336
- try {
337
- const routes = await loadRouteModule(filePath, routesDir);
338
- if (!routes || routes.length === 0) {
339
- return;
340
- }
341
- const existingRoutes = routesByPath.get(filePath);
342
- if (existingRoutes) {
343
- routesByPath.set(filePath, routes);
344
- if (options.onRouteChanged) {
345
- options.onRouteChanged(routes);
346
- }
347
- } else {
348
- routesByPath.set(filePath, routes);
349
- if (options.onRouteAdded) {
350
- options.onRouteAdded(routes);
351
- }
309
+ if (config2.schema) {
310
+ validateSchema(method, config2.schema);
311
+ }
312
+ switch (method) {
313
+ case "GET":
314
+ case "HEAD":
315
+ case "DELETE":
316
+ if (config2.schema?.body) {
317
+ console.warn(`Warning: ${method} requests typically don't have request bodies`);
352
318
  }
353
- } catch (error) {
354
- handleError(error);
355
- }
319
+ break;
356
320
  }
357
- function handleRemoved(filePath) {
358
- const normalizedPath = path3.normalize(filePath);
359
- const routes = routesByPath.get(normalizedPath);
360
- if (routes && routes.length > 0 && options.onRouteRemoved) {
361
- options.onRouteRemoved(normalizedPath, routes);
362
- }
363
- routesByPath.delete(normalizedPath);
321
+ }
322
+ function validateSchema(method, schema) {
323
+ const { params, query, body, response } = schema;
324
+ if (params && (!params._def || typeof params.parse !== "function")) {
325
+ throw new Error(`Params schema for ${method} must be a valid Zod schema`);
364
326
  }
365
- function handleError(error) {
366
- if (options.onError && error instanceof Error) {
367
- options.onError(error);
368
- } else {
369
- console.error("Route watcher error:", error);
370
- }
327
+ if (query && (!query._def || typeof query.parse !== "function")) {
328
+ throw new Error(`Query schema for ${method} must be a valid Zod schema`);
371
329
  }
372
- const watcher = (0, import_chokidar.watch)(routesDir, {
373
- ignored: [
374
- /(^|[/\\])\../,
375
- // Ignore dot files
376
- /node_modules/,
377
- ...options.ignore || []
378
- ],
379
- persistent: true,
380
- ignoreInitial: false,
381
- awaitWriteFinish: {
382
- stabilityThreshold: 300,
383
- pollInterval: 100
384
- }
385
- });
386
- watcher.on("add", loadAndNotify).on("change", loadAndNotify).on("unlink", handleRemoved).on("error", handleError);
387
- loadInitialRoutes().catch(handleError);
388
- return {
389
- /**
390
- * Close the watcher
391
- */
392
- close: () => watcher.close(),
393
- /**
394
- * Get all currently loaded routes (flattened)
395
- */
396
- getRoutes: () => {
397
- const allRoutes = [];
398
- for (const routes of routesByPath.values()) {
399
- allRoutes.push(...routes);
400
- }
401
- return allRoutes;
402
- },
403
- /**
404
- * Get routes organized by file path
405
- */
406
- getRoutesByFile: () => new Map(routesByPath)
407
- };
408
- }
409
-
410
- // src/router/handlers/error.ts
411
- function handleRouteError(ctx, error, options = {}) {
412
- if (options.log) {
413
- console.error("Route error:", error);
330
+ if (body && (!body._def || typeof body.parse !== "function")) {
331
+ throw new Error(`Body schema for ${method} must be a valid Zod schema`);
414
332
  }
415
- const status = getErrorStatus(error);
416
- const response = {
417
- error: getErrorType(error),
418
- message: getErrorMessage(error)
419
- };
420
- if (options.detailed) {
421
- if (error instanceof Error) {
422
- response.stack = error.stack;
423
- }
424
- if (error && typeof error === "object" && "details" in error && error.details) {
425
- response.details = error.details;
426
- }
333
+ if (response && (!response._def || typeof response.parse !== "function")) {
334
+ throw new Error(`Response schema for ${method} must be a valid Zod schema`);
427
335
  }
428
- ctx.response.status(status).json(response);
429
336
  }
430
- function getErrorStatus(error) {
431
- if (error && typeof error === "object") {
432
- if ("status" in error && typeof error.status === "number") {
433
- return error.status;
434
- }
435
- if ("statusCode" in error && typeof error.statusCode === "number") {
436
- return error.statusCode;
437
- }
438
- if ("code" in error && typeof error.code === "string") {
439
- return getStatusFromCode(error.code);
440
- }
337
+
338
+ // src/server/create.ts
339
+ var import_node_async_hooks2 = require("async_hooks");
340
+ var import_node_events = __toESM(require("events"), 1);
341
+
342
+ // src/server/start.ts
343
+ var fs2 = __toESM(require("fs"), 1);
344
+ var http = __toESM(require("http"), 1);
345
+ var http2 = __toESM(require("http2"), 1);
346
+
347
+ // src/server/dev-certificate.ts
348
+ var fs = __toESM(require("fs"), 1);
349
+ var path2 = __toESM(require("path"), 1);
350
+ var selfsigned = __toESM(require("selfsigned"), 1);
351
+ async function generateDevCertificates() {
352
+ const certDir = path2.join(process.cwd(), ".blaizejs", "certs");
353
+ const keyPath = path2.join(certDir, "dev.key");
354
+ const certPath = path2.join(certDir, "dev.cert");
355
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
356
+ return {
357
+ keyFile: keyPath,
358
+ certFile: certPath
359
+ };
441
360
  }
442
- return 500;
443
- }
444
- function getStatusFromCode(code) {
445
- switch (code) {
446
- case "NOT_FOUND":
447
- return 404;
448
- case "UNAUTHORIZED":
449
- return 401;
450
- case "FORBIDDEN":
451
- return 403;
452
- case "BAD_REQUEST":
453
- return 400;
454
- case "CONFLICT":
455
- return 409;
456
- default:
457
- return 500;
361
+ if (!fs.existsSync(certDir)) {
362
+ fs.mkdirSync(certDir, { recursive: true });
458
363
  }
364
+ const attrs = [{ name: "commonName", value: "localhost" }];
365
+ const options = {
366
+ days: 365,
367
+ algorithm: "sha256",
368
+ keySize: 2048,
369
+ extensions: [
370
+ { name: "basicConstraints", cA: true },
371
+ {
372
+ name: "keyUsage",
373
+ keyCertSign: true,
374
+ digitalSignature: true,
375
+ nonRepudiation: true,
376
+ keyEncipherment: true,
377
+ dataEncipherment: true
378
+ },
379
+ {
380
+ name: "extKeyUsage",
381
+ serverAuth: true,
382
+ clientAuth: true
383
+ },
384
+ {
385
+ name: "subjectAltName",
386
+ altNames: [
387
+ { type: 2, value: "localhost" },
388
+ { type: 7, ip: "127.0.0.1" }
389
+ ]
390
+ }
391
+ ]
392
+ };
393
+ const pems = selfsigned.generate(attrs, options);
394
+ fs.writeFileSync(keyPath, Buffer.from(pems.private, "utf-8"));
395
+ fs.writeFileSync(certPath, Buffer.from(pems.cert, "utf-8"));
396
+ console.log(`
397
+ \u{1F512} Generated self-signed certificates for development at ${certDir}
398
+ `);
399
+ return {
400
+ keyFile: keyPath,
401
+ certFile: certPath
402
+ };
459
403
  }
460
- function getErrorType(error) {
461
- if (error && typeof error === "object") {
462
- if ("type" in error && typeof error.type === "string") {
463
- return error.type;
464
- }
465
- if ("name" in error && typeof error.name === "string") {
466
- return error.name;
467
- }
468
- if (error instanceof Error) {
469
- return error.constructor.name;
470
- }
404
+
405
+ // src/context/errors.ts
406
+ var ResponseSentError = class extends Error {
407
+ constructor(message = "\u274C Response has already been sent") {
408
+ super(message);
409
+ this.name = "ResponseSentError";
471
410
  }
472
- return "Error";
473
- }
474
- function getErrorMessage(error) {
475
- if (error instanceof Error) {
476
- return error.message;
411
+ };
412
+ var ResponseSentHeaderError = class extends ResponseSentError {
413
+ constructor(message = "Cannot set header after response has been sent") {
414
+ super(message);
477
415
  }
478
- if (error && typeof error === "object") {
479
- if ("message" in error && typeof error.message === "string") {
480
- return error.message;
481
- }
416
+ };
417
+ var ResponseSentContentError = class extends ResponseSentError {
418
+ constructor(message = "Cannot set content type after response has been sent") {
419
+ super(message);
482
420
  }
483
- return String(error);
484
- }
485
-
486
- // src/router/validation/body.ts
487
- var import_zod = require("zod");
488
- function validateBody(body, schema) {
489
- if (schema instanceof import_zod.z.ZodObject) {
490
- return schema.strict().parse(body);
421
+ };
422
+ var ParseUrlError = class extends ResponseSentError {
423
+ constructor(message = "Invalide URL") {
424
+ super(message);
491
425
  }
492
- return schema.parse(body);
493
- }
426
+ };
494
427
 
495
- // src/router/validation/params.ts
496
- var import_zod2 = require("zod");
497
- function validateParams(params, schema) {
498
- if (schema instanceof import_zod2.z.ZodObject) {
499
- return schema.strict().parse(params);
500
- }
501
- return schema.parse(params);
428
+ // src/context/store.ts
429
+ var import_node_async_hooks = require("async_hooks");
430
+ var contextStorage = new import_node_async_hooks.AsyncLocalStorage();
431
+ function runWithContext(context, callback) {
432
+ return contextStorage.run(context, callback);
502
433
  }
503
434
 
504
- // src/router/validation/query.ts
505
- var import_zod3 = require("zod");
506
- function validateQuery(query, schema) {
507
- if (schema instanceof import_zod3.z.ZodObject) {
508
- return schema.strict().parse(query);
509
- }
510
- return schema.parse(query);
511
- }
512
-
513
- // src/router/validation/response.ts
514
- var import_zod4 = require("zod");
515
- function validateResponse(response, schema) {
516
- if (schema instanceof import_zod4.z.ZodObject) {
517
- return schema.strict().parse(response);
435
+ // src/context/create.ts
436
+ var CONTENT_TYPE_HEADER = "Content-Type";
437
+ function parseRequestUrl(req) {
438
+ const originalUrl = req.url || "/";
439
+ const host = req.headers.host || "localhost";
440
+ const protocol = req.socket && req.socket.encrypted ? "https" : "http";
441
+ const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
442
+ try {
443
+ const url = new URL(fullUrl);
444
+ const path5 = url.pathname;
445
+ const query = {};
446
+ url.searchParams.forEach((value, key) => {
447
+ if (query[key] !== void 0) {
448
+ if (Array.isArray(query[key])) {
449
+ query[key].push(value);
450
+ } else {
451
+ query[key] = [query[key], value];
452
+ }
453
+ } else {
454
+ query[key] = value;
455
+ }
456
+ });
457
+ return { path: path5, url, query };
458
+ } catch (error) {
459
+ console.warn(`Invalid URL: ${fullUrl}`, error);
460
+ throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
518
461
  }
519
- return schema.parse(response);
520
462
  }
521
-
522
- // src/router/validation/schema.ts
523
- function createRequestValidator(schema, debug = false) {
524
- const middlewareFn = async (ctx, next) => {
525
- const errors = {};
526
- if (schema.params && ctx.request.params) {
527
- try {
528
- ctx.request.params = validateParams(ctx.request.params, schema.params);
529
- } catch (error) {
530
- errors.params = formatValidationError(error);
531
- }
532
- }
533
- if (schema.query && ctx.request.query) {
534
- try {
535
- ctx.request.query = validateQuery(ctx.request.query, schema.query);
536
- } catch (error) {
537
- errors.query = formatValidationError(error);
538
- }
539
- }
540
- if (schema.body) {
541
- try {
542
- ctx.request.body = validateBody(ctx.request.body, schema.body);
543
- } catch (error) {
544
- errors.body = formatValidationError(error);
545
- }
546
- }
547
- if (Object.keys(errors).length > 0) {
548
- ctx.response.status(400).json({
549
- error: "Validation Error",
550
- details: errors
551
- });
552
- return;
463
+ function isHttp2Request(req) {
464
+ return "stream" in req || "httpVersionMajor" in req && req.httpVersionMajor === 2;
465
+ }
466
+ function getProtocol(req) {
467
+ const encrypted = req.socket && req.socket.encrypted;
468
+ const forwardedProto = req.headers["x-forwarded-proto"];
469
+ if (forwardedProto) {
470
+ if (Array.isArray(forwardedProto)) {
471
+ return forwardedProto[0]?.split(",")[0]?.trim() || "http";
472
+ } else {
473
+ return forwardedProto.split(",")[0]?.trim() || "http";
553
474
  }
554
- await next();
475
+ }
476
+ return encrypted ? "https" : "http";
477
+ }
478
+ async function createContext(req, res, options = {}) {
479
+ const { path: path5, url, query } = parseRequestUrl(req);
480
+ const method = req.method || "GET";
481
+ const isHttp2 = isHttp2Request(req);
482
+ const protocol = getProtocol(req);
483
+ const params = {};
484
+ const state = { ...options.initialState || {} };
485
+ const responseState = { sent: false };
486
+ const ctx = {
487
+ request: createRequestObject(req, {
488
+ path: path5,
489
+ url,
490
+ query,
491
+ params,
492
+ method,
493
+ isHttp2,
494
+ protocol
495
+ }),
496
+ response: {},
497
+ state
555
498
  };
499
+ ctx.response = createResponseObject(res, responseState, ctx);
500
+ if (options.parseBody) {
501
+ await parseBodyIfNeeded(req, ctx);
502
+ }
503
+ return ctx;
504
+ }
505
+ function createRequestObject(req, info) {
556
506
  return {
557
- name: "RequestValidator",
558
- execute: middlewareFn,
559
- debug
507
+ raw: req,
508
+ ...info,
509
+ header: createRequestHeaderGetter(req),
510
+ headers: createRequestHeadersGetter(req),
511
+ body: void 0
560
512
  };
561
513
  }
562
- function createResponseValidator(responseSchema, debug = false) {
563
- const middlewareFn = async (ctx, next) => {
564
- const originalJson = ctx.response.json;
565
- ctx.response.json = (body, status) => {
566
- try {
567
- const validatedBody = validateResponse(body, responseSchema);
568
- ctx.response.json = originalJson;
569
- return originalJson.call(ctx.response, validatedBody, status);
570
- } catch (error) {
571
- ctx.response.json = originalJson;
572
- console.error("Response validation error:", error);
573
- ctx.response.status(500).json({
574
- error: "Internal Server Error",
575
- message: "Response validation failed"
576
- });
577
- return ctx.response;
578
- }
579
- };
580
- await next();
514
+ function createRequestHeaderGetter(req) {
515
+ return (name) => {
516
+ const value = req.headers[name.toLowerCase()];
517
+ if (Array.isArray(value)) {
518
+ return value.join(", ");
519
+ }
520
+ return value || void 0;
521
+ };
522
+ }
523
+ function createRequestHeadersGetter(req) {
524
+ const headerGetter = createRequestHeaderGetter(req);
525
+ return (names) => {
526
+ if (names && Array.isArray(names) && names.length > 0) {
527
+ return names.reduce((acc, name) => {
528
+ acc[name] = headerGetter(name);
529
+ return acc;
530
+ }, {});
531
+ } else {
532
+ return Object.entries(req.headers).reduce(
533
+ (acc, [key, value]) => {
534
+ acc[key] = Array.isArray(value) ? value.join(", ") : value || void 0;
535
+ return acc;
536
+ },
537
+ {}
538
+ );
539
+ }
581
540
  };
541
+ }
542
+ function createResponseObject(res, responseState, ctx) {
582
543
  return {
583
- name: "ResponseValidator",
584
- execute: middlewareFn,
585
- debug
544
+ raw: res,
545
+ get sent() {
546
+ return responseState.sent;
547
+ },
548
+ status: createStatusSetter(res, responseState, ctx),
549
+ header: createHeaderSetter(res, responseState, ctx),
550
+ headers: createHeadersSetter(res, responseState, ctx),
551
+ type: createContentTypeSetter(res, responseState, ctx),
552
+ json: createJsonResponder(res, responseState),
553
+ text: createTextResponder(res, responseState),
554
+ html: createHtmlResponder(res, responseState),
555
+ redirect: createRedirectResponder(res, responseState),
556
+ stream: createStreamResponder(res, responseState)
586
557
  };
587
558
  }
588
- function formatValidationError(error) {
589
- if (error && typeof error === "object" && "format" in error && typeof error.format === "function") {
590
- return error.format();
591
- }
592
- return error instanceof Error ? error.message : String(error);
559
+ function createStatusSetter(res, responseState, ctx) {
560
+ return function statusSetter(code) {
561
+ if (responseState.sent) {
562
+ throw new ResponseSentError();
563
+ }
564
+ res.statusCode = code;
565
+ return ctx.response;
566
+ };
593
567
  }
594
-
595
- // src/router/handlers/executor.ts
596
- async function executeHandler(ctx, routeOptions, params) {
597
- const middleware = [...routeOptions.middleware || []];
598
- if (routeOptions.schema) {
599
- if (routeOptions.schema.params || routeOptions.schema.query || routeOptions.schema.body) {
600
- middleware.unshift(createRequestValidator(routeOptions.schema));
568
+ function createHeaderSetter(res, responseState, ctx) {
569
+ return function headerSetter(name, value) {
570
+ if (responseState.sent) {
571
+ throw new ResponseSentHeaderError();
601
572
  }
602
- if (routeOptions.schema.response) {
603
- middleware.push(createResponseValidator(routeOptions.schema.response));
573
+ res.setHeader(name, value);
574
+ return ctx.response;
575
+ };
576
+ }
577
+ function createHeadersSetter(res, responseState, ctx) {
578
+ return function headersSetter(headers) {
579
+ if (responseState.sent) {
580
+ throw new ResponseSentHeaderError();
604
581
  }
605
- }
606
- const handler = compose([...middleware]);
607
- await handler(ctx, async () => {
608
- const result = await routeOptions.handler(ctx, params);
609
- if (!ctx.response.sent && result !== void 0) {
610
- ctx.response.json(result);
582
+ for (const [name, value] of Object.entries(headers)) {
583
+ res.setHeader(name, value);
611
584
  }
612
- });
585
+ return ctx.response;
586
+ };
613
587
  }
614
-
615
- // src/router/matching/params.ts
616
- function extractParams(path5, pattern, paramNames) {
617
- const match = pattern.exec(path5);
618
- if (!match) {
619
- return {};
620
- }
621
- const params = {};
622
- for (let i = 0; i < paramNames.length; i++) {
623
- params[paramNames[i]] = match[i + 1] || "";
624
- }
625
- return params;
588
+ function createContentTypeSetter(res, responseState, ctx) {
589
+ return function typeSetter(type) {
590
+ if (responseState.sent) {
591
+ throw new ResponseSentContentError();
592
+ }
593
+ res.setHeader(CONTENT_TYPE_HEADER, type);
594
+ return ctx.response;
595
+ };
626
596
  }
627
- function compilePathPattern(path5) {
628
- const paramNames = [];
629
- if (path5 === "/") {
630
- return {
631
- pattern: /^\/$/,
632
- paramNames: []
633
- };
634
- }
635
- let patternString = path5.replace(/([.+*?^$(){}|\\])/g, "\\$1");
636
- patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
637
- paramNames.push(paramName);
638
- return "/([^/]+)";
639
- }).replace(/\/\[([^\]]+)\]/g, (_, paramName) => {
640
- paramNames.push(paramName);
641
- return "/([^/]+)";
642
- });
643
- patternString = `${patternString}(?:/)?`;
644
- const pattern = new RegExp(`^${patternString}$`);
645
- return {
646
- pattern,
647
- paramNames
597
+ function createJsonResponder(res, responseState) {
598
+ return function jsonResponder(body, status) {
599
+ if (responseState.sent) {
600
+ throw new ResponseSentError();
601
+ }
602
+ if (status !== void 0) {
603
+ res.statusCode = status;
604
+ }
605
+ res.setHeader(CONTENT_TYPE_HEADER, "application/json");
606
+ res.end(JSON.stringify(body));
607
+ responseState.sent = true;
648
608
  };
649
609
  }
650
-
651
- // src/router/matching/matcher.ts
652
- function createMatcher() {
653
- const routes = [];
654
- return {
655
- /**
656
- * Add a route to the matcher
657
- */
658
- add(path5, method, routeOptions) {
659
- const { pattern, paramNames } = compilePathPattern(path5);
660
- const newRoute = {
661
- path: path5,
662
- method,
663
- pattern,
664
- paramNames,
665
- routeOptions
666
- };
667
- const insertIndex = routes.findIndex((route) => paramNames.length < route.paramNames.length);
668
- if (insertIndex === -1) {
669
- routes.push(newRoute);
670
- } else {
671
- routes.splice(insertIndex, 0, newRoute);
672
- }
673
- },
674
- /**
675
- * Match a URL path to a route
676
- */
677
- match(path5, method) {
678
- const pathname = path5.split("?")[0];
679
- if (!pathname) return null;
680
- for (const route of routes) {
681
- if (route.method !== method) continue;
682
- const match = route.pattern.exec(pathname);
683
- if (match) {
684
- const params = extractParams(path5, route.pattern, route.paramNames);
685
- return {
686
- route: route.routeOptions,
687
- params
688
- };
689
- }
690
- }
691
- const matchingPath = routes.find(
692
- (route) => route.method !== method && route.pattern.test(path5)
693
- );
694
- if (matchingPath) {
695
- return {
696
- route: null,
697
- params: {},
698
- methodNotAllowed: true,
699
- allowedMethods: routes.filter((route) => route.pattern.test(path5)).map((route) => route.method)
700
- };
701
- }
702
- return null;
703
- },
704
- /**
705
- * Get all registered routes
706
- */
707
- getRoutes() {
708
- return routes.map((route) => ({
709
- path: route.path,
710
- method: route.method
711
- }));
712
- },
713
- /**
714
- * Find routes matching a specific path
715
- */
716
- findRoutes(path5) {
717
- return routes.filter((route) => route.pattern.test(path5)).map((route) => ({
718
- path: route.path,
719
- method: route.method,
720
- params: extractParams(path5, route.pattern, route.paramNames)
721
- }));
610
+ function createTextResponder(res, responseState) {
611
+ return function textResponder(body, status) {
612
+ if (responseState.sent) {
613
+ throw new ResponseSentError();
722
614
  }
615
+ if (status !== void 0) {
616
+ res.statusCode = status;
617
+ }
618
+ res.setHeader(CONTENT_TYPE_HEADER, "text/plain");
619
+ res.end(body);
620
+ responseState.sent = true;
723
621
  };
724
622
  }
725
-
726
- // src/router/router.ts
727
- var DEFAULT_ROUTER_OPTIONS = {
728
- routesDir: "./routes",
729
- basePath: "/",
730
- watchMode: process.env.NODE_ENV === "development"
731
- };
732
- function createRouter(options) {
733
- const routerOptions = {
734
- ...DEFAULT_ROUTER_OPTIONS,
735
- ...options
623
+ function createHtmlResponder(res, responseState) {
624
+ return function htmlResponder(body, status) {
625
+ if (responseState.sent) {
626
+ throw new ResponseSentError();
627
+ }
628
+ if (status !== void 0) {
629
+ res.statusCode = status;
630
+ }
631
+ res.setHeader(CONTENT_TYPE_HEADER, "text/html");
632
+ res.end(body);
633
+ responseState.sent = true;
736
634
  };
737
- if (options.basePath && !options.basePath.startsWith("/")) {
738
- console.warn("Base path does nothing");
739
- }
740
- const routes = [];
741
- const matcher = createMatcher();
742
- let initialized = false;
743
- let initializationPromise = null;
744
- let _watchers = null;
745
- const routeSources = /* @__PURE__ */ new Map();
746
- const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
747
- function addRouteWithSource(route, source) {
748
- const existingSources = routeSources.get(route.path) || [];
749
- if (existingSources.includes(source)) {
750
- console.warn(`Skipping duplicate route: ${route.path} from ${source}`);
751
- return;
635
+ }
636
+ function createRedirectResponder(res, responseState) {
637
+ return function redirectResponder(url, status = 302) {
638
+ if (responseState.sent) {
639
+ throw new ResponseSentError();
752
640
  }
753
- if (existingSources.length > 0) {
754
- const conflictError = new Error(
755
- `Route conflict for path "${route.path}": already defined in ${existingSources.join(", ")}, now being added from ${source}`
756
- );
757
- console.error(conflictError.message);
758
- throw conflictError;
641
+ res.statusCode = status;
642
+ res.setHeader("Location", url);
643
+ res.end();
644
+ responseState.sent = true;
645
+ };
646
+ }
647
+ function createStreamResponder(res, responseState) {
648
+ return function streamResponder(readable, options = {}) {
649
+ if (responseState.sent) {
650
+ throw new ResponseSentError();
759
651
  }
760
- routeSources.set(route.path, [...existingSources, source]);
761
- addRouteInternal(route);
762
- }
763
- async function loadRoutesFromDirectory(directory, source, prefix) {
764
- try {
765
- const discoveredRoutes = await findRoutes(directory, {
766
- basePath: routerOptions.basePath
767
- });
768
- for (const route of discoveredRoutes) {
769
- const finalRoute = prefix ? {
770
- ...route,
771
- path: `${prefix}${route.path}`
772
- } : route;
773
- addRouteWithSource(finalRoute, source);
774
- }
775
- console.log(
776
- `Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
777
- );
778
- } catch (error) {
779
- console.error(`Failed to load routes from ${source}:`, error);
780
- throw error;
652
+ if (options.status !== void 0) {
653
+ res.statusCode = options.status;
781
654
  }
782
- }
783
- async function initialize() {
784
- if (initialized || initializationPromise) {
785
- return initializationPromise;
655
+ if (options.contentType) {
656
+ res.setHeader(CONTENT_TYPE_HEADER, options.contentType);
786
657
  }
787
- initializationPromise = (async () => {
788
- try {
789
- for (const directory of routeDirectories) {
790
- await loadRoutesFromDirectory(directory, directory);
791
- }
792
- if (routerOptions.watchMode) {
793
- setupWatcherForAllDirectories();
794
- }
795
- initialized = true;
796
- } catch (error) {
797
- console.error("Failed to initialize router:", error);
798
- throw error;
658
+ if (options.headers) {
659
+ for (const [name, value] of Object.entries(options.headers)) {
660
+ res.setHeader(name, value);
799
661
  }
800
- })();
801
- return initializationPromise;
802
- }
803
- function addRouteInternal(route) {
804
- routes.push(route);
805
- Object.entries(route).forEach(([method, methodOptions]) => {
806
- if (method === "path" || !methodOptions) return;
807
- matcher.add(route.path, method, methodOptions);
662
+ }
663
+ readable.pipe(res);
664
+ readable.on("end", () => {
665
+ responseState.sent = true;
808
666
  });
809
- }
810
- function createWatcherCallbacks(directory, source, prefix) {
811
- return {
812
- onRouteAdded: (addedRoutes) => {
813
- console.log(
814
- `${addedRoutes.length} route(s) added from ${directory}:`,
815
- addedRoutes.map((r) => r.path)
816
- );
817
- addedRoutes.forEach((route) => {
818
- const finalRoute = prefix ? { ...route, path: `${prefix}${route.path}` } : route;
819
- addRouteWithSource(finalRoute, source);
820
- });
821
- },
822
- onRouteChanged: (changedRoutes) => {
823
- console.log(
824
- `${changedRoutes.length} route(s) changed in ${directory}:`,
825
- changedRoutes.map((r) => r.path)
826
- );
827
- changedRoutes.forEach((route) => {
828
- const finalPath = prefix ? `${prefix}${route.path}` : route.path;
829
- const index = routes.findIndex((r) => r.path === finalPath);
830
- if (index >= 0) {
831
- routes.splice(index, 1);
832
- const sources = routeSources.get(finalPath) || [];
833
- const filteredSources = sources.filter((s) => s !== source);
834
- if (filteredSources.length > 0) {
835
- routeSources.set(finalPath, filteredSources);
836
- } else {
837
- routeSources.delete(finalPath);
838
- }
839
- }
840
- const finalRoute = prefix ? { ...route, path: finalPath } : route;
841
- addRouteWithSource(finalRoute, source);
842
- });
843
- },
844
- onRouteRemoved: (filePath, removedRoutes) => {
845
- console.log(
846
- `File removed from ${directory}: ${filePath} with ${removedRoutes.length} route(s):`,
847
- removedRoutes.map((r) => r.path)
848
- );
849
- removedRoutes.forEach((route) => {
850
- const finalPath = prefix ? `${prefix}${route.path}` : route.path;
851
- const index = routes.findIndex((r) => r.path === finalPath);
852
- if (index >= 0) {
853
- routes.splice(index, 1);
854
- }
855
- const sources = routeSources.get(finalPath) || [];
856
- const filteredSources = sources.filter((s) => s !== source);
857
- if (filteredSources.length > 0) {
858
- routeSources.set(finalPath, filteredSources);
859
- } else {
860
- routeSources.delete(finalPath);
861
- }
862
- });
863
- },
864
- onError: (error) => {
865
- console.error(`Route watcher error for ${directory}:`, error);
667
+ readable.on("error", (err) => {
668
+ console.error("Stream error:", err);
669
+ if (!responseState.sent) {
670
+ res.statusCode = 500;
671
+ res.end("Stream error");
672
+ responseState.sent = true;
866
673
  }
867
- };
868
- }
869
- function setupWatcherForDirectory(directory, source, prefix) {
870
- const callbacks = createWatcherCallbacks(directory, source, prefix);
871
- const watcher = watchRoutes(directory, {
872
- ignore: ["node_modules", ".git"],
873
- ...callbacks
874
674
  });
875
- if (!_watchers) {
876
- _watchers = /* @__PURE__ */ new Map();
877
- }
878
- _watchers.set(directory, watcher);
879
- return watcher;
675
+ };
676
+ }
677
+ async function parseBodyIfNeeded(req, ctx) {
678
+ if (shouldSkipParsing(req.method)) {
679
+ return;
880
680
  }
881
- function setupWatcherForAllDirectories() {
882
- for (const directory of routeDirectories) {
883
- setupWatcherForDirectory(directory, directory);
681
+ const contentType = req.headers["content-type"] || "";
682
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
683
+ if (contentLength === 0 || contentLength > 1048576) {
684
+ return;
685
+ }
686
+ try {
687
+ await parseBodyByContentType(req, ctx, contentType);
688
+ } catch (error) {
689
+ setBodyError(ctx, "body_read_error", "Error reading request body", error);
690
+ }
691
+ }
692
+ function shouldSkipParsing(method) {
693
+ const skipMethods = ["GET", "HEAD", "OPTIONS"];
694
+ return skipMethods.includes(method || "GET");
695
+ }
696
+ async function parseBodyByContentType(req, ctx, contentType) {
697
+ if (contentType.includes("application/json")) {
698
+ await parseJsonBody(req, ctx);
699
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
700
+ await parseFormUrlEncodedBody(req, ctx);
701
+ } else if (contentType.includes("text/")) {
702
+ await parseTextBody(req, ctx);
703
+ }
704
+ }
705
+ async function parseJsonBody(req, ctx) {
706
+ const body = await readRequestBody(req);
707
+ if (!body) {
708
+ console.warn("Empty body, skipping JSON parsing");
709
+ return;
710
+ }
711
+ if (body.trim() === "null") {
712
+ console.warn('Body is the string "null"');
713
+ ctx.request.body = null;
714
+ return;
715
+ }
716
+ try {
717
+ const json = JSON.parse(body);
718
+ ctx.request.body = json;
719
+ } catch (error) {
720
+ ctx.request.body = null;
721
+ setBodyError(ctx, "json_parse_error", "Invalid JSON in request body", error);
722
+ }
723
+ }
724
+ async function parseFormUrlEncodedBody(req, ctx) {
725
+ const body = await readRequestBody(req);
726
+ if (!body) return;
727
+ try {
728
+ ctx.request.body = parseUrlEncodedData(body);
729
+ } catch (error) {
730
+ ctx.request.body = null;
731
+ setBodyError(ctx, "form_parse_error", "Invalid form data in request body", error);
732
+ }
733
+ }
734
+ function parseUrlEncodedData(body) {
735
+ const params = new URLSearchParams(body);
736
+ const formData = {};
737
+ params.forEach((value, key) => {
738
+ if (formData[key] !== void 0) {
739
+ if (Array.isArray(formData[key])) {
740
+ formData[key].push(value);
741
+ } else {
742
+ formData[key] = [formData[key], value];
743
+ }
744
+ } else {
745
+ formData[key] = value;
884
746
  }
747
+ });
748
+ return formData;
749
+ }
750
+ async function parseTextBody(req, ctx) {
751
+ const body = await readRequestBody(req);
752
+ if (body) {
753
+ ctx.request.body = body;
885
754
  }
886
- initialize().catch((error) => {
887
- console.error("Failed to initialize router on creation:", error);
755
+ }
756
+ function setBodyError(ctx, type, message, error) {
757
+ ctx.state._bodyError = { type, message, error };
758
+ }
759
+ async function readRequestBody(req) {
760
+ return new Promise((resolve2, reject) => {
761
+ const chunks = [];
762
+ req.on("data", (chunk) => {
763
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
764
+ });
765
+ req.on("end", () => {
766
+ resolve2(Buffer.concat(chunks).toString("utf8"));
767
+ });
768
+ req.on("error", (err) => {
769
+ reject(err);
770
+ });
888
771
  });
889
- return {
890
- /**
891
- * Handle an incoming request
892
- */
893
- async handleRequest(ctx) {
894
- if (!initialized) {
895
- await initialize();
896
- }
897
- const { method, path: path5 } = ctx.request;
898
- const match = matcher.match(path5, method);
899
- if (!match) {
900
- ctx.response.status(404).json({ error: "Not Found" });
901
- return;
902
- }
903
- if (match.methodNotAllowed) {
904
- ctx.response.status(405).json({
905
- error: "Method Not Allowed",
906
- allowed: match.allowedMethods
907
- });
908
- if (match.allowedMethods && match.allowedMethods.length > 0) {
909
- ctx.response.header("Allow", match.allowedMethods.join(", "));
910
- }
911
- return;
912
- }
913
- ctx.request.params = match.params;
914
- try {
915
- await executeHandler(ctx, match.route, match.params);
916
- } catch (error) {
917
- handleRouteError(ctx, error, {
918
- detailed: process.env.NODE_ENV !== "production",
919
- log: true
920
- });
921
- }
922
- },
923
- /**
924
- * Get all registered routes
925
- */
926
- getRoutes() {
927
- return [...routes];
928
- },
929
- /**
930
- * Add a route programmatically
931
- */
932
- addRoute(route) {
933
- addRouteInternal(route);
934
- },
935
- /**
936
- * Add a route directory (for plugins)
937
- */
938
- async addRouteDirectory(directory, options2 = {}) {
939
- if (routeDirectories.has(directory)) {
940
- console.warn(`Route directory ${directory} already registered`);
941
- return;
942
- }
943
- routeDirectories.add(directory);
944
- if (initialized) {
945
- await loadRoutesFromDirectory(directory, directory, options2.prefix);
946
- if (routerOptions.watchMode) {
947
- setupWatcherForDirectory(directory, directory, options2.prefix);
948
- }
949
- }
950
- },
951
- /**
952
- * Get route conflicts
953
- */
954
- getRouteConflicts() {
955
- const conflicts = [];
956
- for (const [path5, sources] of routeSources.entries()) {
957
- if (sources.length > 1) {
958
- conflicts.push({ path: path5, sources });
772
+ }
773
+
774
+ // src/server/request-handler.ts
775
+ function createRequestHandler(serverInstance) {
776
+ return async (req, res) => {
777
+ try {
778
+ const context = await createContext(req, res, {
779
+ parseBody: true
780
+ // Enable automatic body parsing
781
+ });
782
+ const handler = compose(serverInstance.middleware);
783
+ await runWithContext(context, async () => {
784
+ try {
785
+ await handler(context, async () => {
786
+ if (!context.response.sent) {
787
+ await serverInstance.router.handleRequest(context);
788
+ if (!context.response.sent) {
789
+ context.response.status(404).json({
790
+ error: "Not Found",
791
+ message: `Route not found: ${context.request.method} ${context.request.path}`
792
+ });
793
+ }
794
+ }
795
+ });
796
+ } catch (error) {
797
+ console.error("Error processing request:", error);
798
+ if (!context.response.sent) {
799
+ context.response.json(
800
+ {
801
+ error: "Internal Server Error",
802
+ message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
803
+ },
804
+ 500
805
+ );
806
+ }
959
807
  }
960
- }
961
- return conflicts;
808
+ });
809
+ } catch (error) {
810
+ console.error("Error creating context:", error);
811
+ res.writeHead(500, { "Content-Type": "application/json" });
812
+ res.end(
813
+ JSON.stringify({
814
+ error: "Internal Server Error",
815
+ message: "Failed to process request"
816
+ })
817
+ );
962
818
  }
963
819
  };
964
820
  }
965
821
 
966
- // src/config.ts
967
- var config = {};
968
- function setRuntimeConfig(newConfig) {
969
- config = { ...config, ...newConfig };
970
- }
971
- function getRoutesDir() {
972
- if (!config.routesDir) {
973
- throw new Error("Routes directory not configured. Make sure server is properly initialized.");
822
+ // src/server/start.ts
823
+ async function prepareCertificates(http2Options) {
824
+ if (!http2Options.enabled) {
825
+ return {};
974
826
  }
975
- return config.routesDir;
827
+ const { keyFile, certFile } = http2Options;
828
+ const isDevMode = process.env.NODE_ENV === "development";
829
+ const certificatesMissing = !keyFile || !certFile;
830
+ if (certificatesMissing && isDevMode) {
831
+ const devCerts = await generateDevCertificates();
832
+ return devCerts;
833
+ }
834
+ if (certificatesMissing) {
835
+ throw new Error(
836
+ "HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically."
837
+ );
838
+ }
839
+ return { keyFile, certFile };
976
840
  }
977
-
978
- // src/router/create.ts
979
- function getCallerFilePath() {
980
- const originalPrepareStackTrace = Error.prepareStackTrace;
841
+ function createServerInstance(isHttp2, certOptions) {
842
+ if (!isHttp2) {
843
+ return http.createServer();
844
+ }
845
+ const http2ServerOptions = {
846
+ allowHTTP1: true
847
+ // Allow fallback to HTTP/1.1
848
+ };
981
849
  try {
982
- Error.prepareStackTrace = (_, stack2) => stack2;
983
- const stack = new Error().stack;
984
- const callerFrame = stack[3];
985
- if (!callerFrame || typeof callerFrame.getFileName !== "function") {
986
- throw new Error("Unable to determine caller file frame");
850
+ if (certOptions.keyFile) {
851
+ http2ServerOptions.key = fs2.readFileSync(certOptions.keyFile);
987
852
  }
988
- const fileName = callerFrame.getFileName();
989
- if (!fileName) {
990
- throw new Error("Unable to determine caller file name");
853
+ if (certOptions.certFile) {
854
+ http2ServerOptions.cert = fs2.readFileSync(certOptions.certFile);
991
855
  }
992
- return fileName;
993
- } finally {
994
- Error.prepareStackTrace = originalPrepareStackTrace;
856
+ } catch (err) {
857
+ throw new Error(
858
+ `Failed to read certificate files: ${err instanceof Error ? err.message : String(err)}`
859
+ );
995
860
  }
861
+ return http2.createSecureServer(http2ServerOptions);
996
862
  }
997
- function getRoutePath() {
998
- console.log("getRoutePath called");
999
- const callerPath = getCallerFilePath();
1000
- const routesDir = getRoutesDir();
1001
- const parsedRoute = parseRoutePath(callerPath, routesDir);
1002
- console.log(`Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
1003
- return parsedRoute.routePath;
863
+ function listenOnPort(server, port, host, isHttp2) {
864
+ return new Promise((resolve2, reject) => {
865
+ server.listen(port, host, () => {
866
+ const protocol = isHttp2 ? "https" : "http";
867
+ const url = `${protocol}://${host}:${port}`;
868
+ console.log(`
869
+ \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}
870
+
871
+ \u26A1 BlaizeJS DEVELOPMENT SERVER HOT AND READY \u26A1
872
+
873
+ \u{1F680} Server: ${url}
874
+ \u{1F525} Hot Reload: Enabled
875
+ \u{1F6E0}\uFE0F Mode: Development
876
+
877
+ Time to build something amazing! \u{1F680}
878
+
879
+ \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}
880
+ `);
881
+ resolve2();
882
+ });
883
+ server.on("error", (err) => {
884
+ console.error("Server error:", err);
885
+ reject(err);
886
+ });
887
+ });
1004
888
  }
1005
- var createGetRoute = (config2) => {
1006
- validateMethodConfig("GET", config2);
1007
- const path5 = getRoutePath();
1008
- return {
1009
- GET: config2,
1010
- path: path5
1011
- };
1012
- };
1013
- var createPostRoute = (config2) => {
1014
- validateMethodConfig("POST", config2);
1015
- const path5 = getRoutePath();
1016
- return {
1017
- POST: config2,
1018
- path: path5
1019
- };
1020
- };
1021
- var createPutRoute = (config2) => {
1022
- validateMethodConfig("PUT", config2);
1023
- const path5 = getRoutePath();
1024
- return {
1025
- PUT: config2,
1026
- path: path5
1027
- };
1028
- };
1029
- var createDeleteRoute = (config2) => {
1030
- validateMethodConfig("DELETE", config2);
1031
- const path5 = getRoutePath();
1032
- return {
1033
- DELETE: config2,
1034
- path: path5
1035
- };
1036
- };
1037
- var createPatchRoute = (config2) => {
1038
- validateMethodConfig("PATCH", config2);
1039
- const path5 = getRoutePath();
1040
- return {
1041
- PATCH: config2,
1042
- path: path5
1043
- };
1044
- };
1045
- var createHeadRoute = (config2) => {
1046
- validateMethodConfig("HEAD", config2);
1047
- const path5 = getRoutePath();
1048
- return {
1049
- HEAD: config2,
1050
- path: path5
1051
- };
1052
- };
1053
- var createOptionsRoute = (config2) => {
1054
- validateMethodConfig("OPTIONS", config2);
1055
- const path5 = getRoutePath();
1056
- return {
1057
- OPTIONS: config2,
1058
- path: path5
1059
- };
1060
- };
1061
- function validateMethodConfig(method, config2) {
1062
- if (!config2.handler || typeof config2.handler !== "function") {
1063
- throw new Error(`Handler for method ${method} must be a function`);
889
+ async function initializePlugins(serverInstance) {
890
+ for (const plugin of serverInstance.plugins) {
891
+ if (typeof plugin.initialize === "function") {
892
+ await plugin.initialize(serverInstance);
893
+ }
1064
894
  }
1065
- if (config2.middleware && !Array.isArray(config2.middleware)) {
1066
- throw new Error(`Middleware for method ${method} must be an array`);
895
+ }
896
+ async function startServer(serverInstance, serverOptions) {
897
+ if (serverInstance.server) {
898
+ return;
1067
899
  }
1068
- if (config2.schema) {
1069
- validateSchema(method, config2.schema);
900
+ try {
901
+ const port = serverOptions.port;
902
+ const host = serverOptions.host;
903
+ await initializePlugins(serverInstance);
904
+ const http2Options = serverOptions.http2 || { enabled: true };
905
+ const isHttp2 = !!http2Options.enabled;
906
+ const certOptions = await prepareCertificates(http2Options);
907
+ if (serverOptions.http2 && certOptions.keyFile && certOptions.certFile) {
908
+ serverOptions.http2.keyFile = certOptions.keyFile;
909
+ serverOptions.http2.certFile = certOptions.certFile;
910
+ }
911
+ const server = createServerInstance(isHttp2, certOptions);
912
+ serverInstance.server = server;
913
+ serverInstance.port = port;
914
+ serverInstance.host = host;
915
+ const requestHandler = createRequestHandler(serverInstance);
916
+ server.on("request", requestHandler);
917
+ await listenOnPort(server, port, host, isHttp2);
918
+ } catch (error) {
919
+ console.error("Failed to start server:", error);
920
+ throw error;
1070
921
  }
1071
- switch (method) {
1072
- case "GET":
1073
- case "HEAD":
1074
- case "DELETE":
1075
- if (config2.schema?.body) {
1076
- console.warn(`Warning: ${method} requests typically don't have request bodies`);
1077
- }
1078
- break;
922
+ }
923
+
924
+ // src/server/stop.ts
925
+ async function stopServer(serverInstance, options = {}) {
926
+ const server = serverInstance.server;
927
+ const events = serverInstance.events;
928
+ if (!server) {
929
+ return;
930
+ }
931
+ const timeout = options.timeout || 3e4;
932
+ try {
933
+ if (options.onStopping) {
934
+ await options.onStopping();
935
+ }
936
+ events.emit("stopping");
937
+ await serverInstance.pluginManager.onServerStop(serverInstance, server);
938
+ const timeoutPromise = new Promise((_, reject) => {
939
+ setTimeout(() => {
940
+ reject(new Error("Server shutdown timed out waiting for requests to complete"));
941
+ }, timeout);
942
+ });
943
+ const closePromise = new Promise((resolve2, reject) => {
944
+ server.close((err) => {
945
+ if (err) {
946
+ return reject(err);
947
+ }
948
+ resolve2();
949
+ });
950
+ });
951
+ await Promise.race([closePromise, timeoutPromise]);
952
+ await serverInstance.pluginManager.terminatePlugins(serverInstance);
953
+ if (options.onStopped) {
954
+ await options.onStopped();
955
+ }
956
+ events.emit("stopped");
957
+ serverInstance.server = null;
958
+ } catch (error) {
959
+ events.emit("error", error);
960
+ throw error;
1079
961
  }
1080
962
  }
1081
- function validateSchema(method, schema) {
1082
- const { params, query, body, response } = schema;
1083
- if (params && (!params._def || typeof params.parse !== "function")) {
1084
- throw new Error(`Params schema for ${method} must be a valid Zod schema`);
963
+ function registerSignalHandlers(stopFn) {
964
+ const sigintHandler = () => stopFn().catch(console.error);
965
+ const sigtermHandler = () => stopFn().catch(console.error);
966
+ process.on("SIGINT", sigintHandler);
967
+ process.on("SIGTERM", sigtermHandler);
968
+ return {
969
+ unregister: () => {
970
+ process.removeListener("SIGINT", sigintHandler);
971
+ process.removeListener("SIGTERM", sigtermHandler);
972
+ }
973
+ };
974
+ }
975
+
976
+ // src/server/validation.ts
977
+ var import_zod = require("zod");
978
+ var middlewareSchema = import_zod.z.custom(
979
+ (data) => data !== null && typeof data === "object" && "execute" in data && typeof data.execute === "function",
980
+ {
981
+ message: "Expected middleware to have an execute function"
1085
982
  }
1086
- if (query && (!query._def || typeof query.parse !== "function")) {
1087
- throw new Error(`Query schema for ${method} must be a valid Zod schema`);
983
+ );
984
+ var pluginSchema = import_zod.z.custom(
985
+ (data) => data !== null && typeof data === "object" && "register" in data && typeof data.register === "function",
986
+ {
987
+ message: "Expected a valid plugin object with a register method"
1088
988
  }
1089
- if (body && (!body._def || typeof body.parse !== "function")) {
1090
- throw new Error(`Body schema for ${method} must be a valid Zod schema`);
989
+ );
990
+ var http2Schema = import_zod.z.object({
991
+ enabled: import_zod.z.boolean().optional().default(true),
992
+ keyFile: import_zod.z.string().optional(),
993
+ certFile: import_zod.z.string().optional()
994
+ }).refine(
995
+ (data) => {
996
+ if (data.enabled && process.env.NODE_ENV === "production") {
997
+ return data.keyFile && data.certFile;
998
+ }
999
+ return true;
1000
+ },
1001
+ {
1002
+ message: "When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"
1091
1003
  }
1092
- if (response && (!response._def || typeof response.parse !== "function")) {
1093
- throw new Error(`Response schema for ${method} must be a valid Zod schema`);
1004
+ );
1005
+ var serverOptionsSchema = import_zod.z.object({
1006
+ port: import_zod.z.number().int().positive().optional().default(3e3),
1007
+ host: import_zod.z.string().optional().default("localhost"),
1008
+ routesDir: import_zod.z.string().optional().default("./routes"),
1009
+ http2: http2Schema.optional().default({
1010
+ enabled: true
1011
+ }),
1012
+ middleware: import_zod.z.array(middlewareSchema).optional().default([]),
1013
+ plugins: import_zod.z.array(pluginSchema).optional().default([])
1014
+ });
1015
+ function validateServerOptions(options) {
1016
+ try {
1017
+ return serverOptionsSchema.parse(options);
1018
+ } catch (error) {
1019
+ if (error instanceof import_zod.z.ZodError) {
1020
+ const formattedError = error.format();
1021
+ throw new Error(`Invalid server options: ${JSON.stringify(formattedError, null, 2)}`);
1022
+ }
1023
+ throw new Error(`Invalid server options: ${String(error)}`);
1094
1024
  }
1095
1025
  }
1096
1026
 
1097
- // src/server/create.ts
1098
- var import_node_async_hooks2 = require("async_hooks");
1099
- var import_node_events = __toESM(require("events"), 1);
1100
-
1101
- // src/server/start.ts
1102
- var fs3 = __toESM(require("fs"), 1);
1103
- var http = __toESM(require("http"), 1);
1104
- var http2 = __toESM(require("http2"), 1);
1105
-
1106
- // src/server/dev-certificate.ts
1107
- var fs2 = __toESM(require("fs"), 1);
1108
- var path4 = __toESM(require("path"), 1);
1109
- var selfsigned = __toESM(require("selfsigned"), 1);
1110
- async function generateDevCertificates() {
1111
- const certDir = path4.join(process.cwd(), ".blaizejs", "certs");
1112
- const keyPath = path4.join(certDir, "dev.key");
1113
- const certPath = path4.join(certDir, "dev.cert");
1114
- if (fs2.existsSync(keyPath) && fs2.existsSync(certPath)) {
1115
- return {
1116
- keyFile: keyPath,
1117
- certFile: certPath
1118
- };
1027
+ // src/plugins/lifecycle.ts
1028
+ function createPluginLifecycleManager(options = {}) {
1029
+ const { continueOnError = true, debug = false, onError } = options;
1030
+ function log(message, ...args) {
1031
+ if (debug) {
1032
+ console.log(`[PluginLifecycle] ${message}`, ...args);
1033
+ }
1119
1034
  }
1120
- if (!fs2.existsSync(certDir)) {
1121
- fs2.mkdirSync(certDir, { recursive: true });
1035
+ function handleError(plugin, phase, error) {
1036
+ const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
1037
+ if (onError) {
1038
+ onError(plugin, phase, error);
1039
+ } else {
1040
+ console.error(errorMessage, error);
1041
+ }
1042
+ if (!continueOnError) {
1043
+ throw new Error(errorMessage);
1044
+ }
1122
1045
  }
1123
- const attrs = [{ name: "commonName", value: "localhost" }];
1124
- const options = {
1125
- days: 365,
1126
- algorithm: "sha256",
1127
- keySize: 2048,
1128
- extensions: [
1129
- { name: "basicConstraints", cA: true },
1130
- {
1131
- name: "keyUsage",
1132
- keyCertSign: true,
1133
- digitalSignature: true,
1134
- nonRepudiation: true,
1135
- keyEncipherment: true,
1136
- dataEncipherment: true
1137
- },
1138
- {
1139
- name: "extKeyUsage",
1140
- serverAuth: true,
1141
- clientAuth: true
1142
- },
1143
- {
1144
- name: "subjectAltName",
1145
- altNames: [
1146
- { type: 2, value: "localhost" },
1147
- { type: 7, ip: "127.0.0.1" }
1148
- ]
1149
- }
1150
- ]
1151
- };
1152
- const pems = selfsigned.generate(attrs, options);
1153
- fs2.writeFileSync(keyPath, Buffer.from(pems.private, "utf-8"));
1154
- fs2.writeFileSync(certPath, Buffer.from(pems.cert, "utf-8"));
1155
- console.log(`
1156
- \u{1F512} Generated self-signed certificates for development at ${certDir}
1157
- `);
1158
1046
  return {
1159
- keyFile: keyPath,
1160
- certFile: certPath
1047
+ /**
1048
+ * Initialize all plugins
1049
+ */
1050
+ async initializePlugins(server) {
1051
+ log("Initializing plugins...");
1052
+ for (const plugin of server.plugins) {
1053
+ if (plugin.initialize) {
1054
+ try {
1055
+ log(`Initializing plugin: ${plugin.name}`);
1056
+ await plugin.initialize(server);
1057
+ } catch (error) {
1058
+ handleError(plugin, "initialize", error);
1059
+ }
1060
+ }
1061
+ }
1062
+ log(`Initialized ${server.plugins.length} plugins`);
1063
+ },
1064
+ /**
1065
+ * Terminate all plugins in reverse order
1066
+ */
1067
+ async terminatePlugins(server) {
1068
+ log("Terminating plugins...");
1069
+ const pluginsToTerminate = [...server.plugins].reverse();
1070
+ for (const plugin of pluginsToTerminate) {
1071
+ if (plugin.terminate) {
1072
+ try {
1073
+ log(`Terminating plugin: ${plugin.name}`);
1074
+ await plugin.terminate(server);
1075
+ } catch (error) {
1076
+ handleError(plugin, "terminate", error);
1077
+ }
1078
+ }
1079
+ }
1080
+ log(`Terminated ${pluginsToTerminate.length} plugins`);
1081
+ },
1082
+ /**
1083
+ * Notify plugins that the server has started
1084
+ */
1085
+ async onServerStart(server, httpServer) {
1086
+ log("Notifying plugins of server start...");
1087
+ for (const plugin of server.plugins) {
1088
+ if (plugin.onServerStart) {
1089
+ try {
1090
+ log(`Notifying plugin of server start: ${plugin.name}`);
1091
+ await plugin.onServerStart(httpServer);
1092
+ } catch (error) {
1093
+ handleError(plugin, "onServerStart", error);
1094
+ }
1095
+ }
1096
+ }
1097
+ },
1098
+ /**
1099
+ * Notify plugins that the server is stopping
1100
+ */
1101
+ async onServerStop(server, httpServer) {
1102
+ log("Notifying plugins of server stop...");
1103
+ const pluginsToNotify = [...server.plugins].reverse();
1104
+ for (const plugin of pluginsToNotify) {
1105
+ if (plugin.onServerStop) {
1106
+ try {
1107
+ log(`Notifying plugin of server stop: ${plugin.name}`);
1108
+ await plugin.onServerStop(httpServer);
1109
+ } catch (error) {
1110
+ handleError(plugin, "onServerStop", error);
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1161
1115
  };
1162
1116
  }
1163
1117
 
1164
- // src/context/errors.ts
1165
- var ResponseSentError = class extends Error {
1166
- constructor(message = "\u274C Response has already been sent") {
1167
- super(message);
1168
- this.name = "ResponseSentError";
1118
+ // src/plugins/errors.ts
1119
+ var PluginValidationError = class extends Error {
1120
+ constructor(pluginName, message) {
1121
+ super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
1122
+ this.pluginName = pluginName;
1123
+ this.name = "PluginValidationError";
1169
1124
  }
1170
1125
  };
1171
- var ResponseSentHeaderError = class extends ResponseSentError {
1172
- constructor(message = "Cannot set header after response has been sent") {
1173
- super(message);
1126
+
1127
+ // src/plugins/validation.ts
1128
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
1129
+ "core",
1130
+ "server",
1131
+ "router",
1132
+ "middleware",
1133
+ "context",
1134
+ "blaize",
1135
+ "blaizejs"
1136
+ ]);
1137
+ var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
1138
+ var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
1139
+ function validatePlugin(plugin, options = {}) {
1140
+ const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
1141
+ if (!plugin || typeof plugin !== "object") {
1142
+ throw new PluginValidationError("", "Plugin must be an object");
1174
1143
  }
1175
- };
1176
- var ResponseSentContentError = class extends ResponseSentError {
1177
- constructor(message = "Cannot set content type after response has been sent") {
1178
- super(message);
1144
+ const p = plugin;
1145
+ if (!p.name || typeof p.name !== "string") {
1146
+ throw new PluginValidationError("", "Plugin must have a name (string)");
1179
1147
  }
1180
- };
1181
- var ParseUrlError = class extends ResponseSentError {
1182
- constructor(message = "Invalide URL") {
1183
- super(message);
1148
+ if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
1149
+ throw new PluginValidationError(
1150
+ p.name,
1151
+ "Plugin name must be lowercase letters, numbers, and hyphens only"
1152
+ );
1153
+ }
1154
+ if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
1155
+ throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
1156
+ }
1157
+ if (requireVersion) {
1158
+ if (!p.version || typeof p.version !== "string") {
1159
+ throw new PluginValidationError(p.name, "Plugin must have a version (string)");
1160
+ }
1161
+ if (!VALID_VERSION_PATTERN.test(p.version)) {
1162
+ throw new PluginValidationError(
1163
+ p.name,
1164
+ 'Plugin version must follow semantic versioning (e.g., "1.0.0")'
1165
+ );
1166
+ }
1167
+ }
1168
+ if (!p.register || typeof p.register !== "function") {
1169
+ throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
1170
+ }
1171
+ const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
1172
+ for (const method of lifecycleMethods) {
1173
+ if (p[method] && typeof p[method] !== "function") {
1174
+ throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ // src/router/discovery/finder.ts
1180
+ var fs3 = __toESM(require("fs/promises"), 1);
1181
+ var path3 = __toESM(require("path"), 1);
1182
+ async function findRouteFiles(routesDir, options = {}) {
1183
+ const absoluteDir = path3.isAbsolute(routesDir) ? routesDir : path3.resolve(process.cwd(), routesDir);
1184
+ console.log("Creating router with routes directory:", absoluteDir);
1185
+ try {
1186
+ const stats = await fs3.stat(absoluteDir);
1187
+ if (!stats.isDirectory()) {
1188
+ throw new Error(`Route directory is not a directory: ${absoluteDir}`);
1189
+ }
1190
+ } catch (error) {
1191
+ if (error.code === "ENOENT") {
1192
+ throw new Error(`Route directory not found: ${absoluteDir}`);
1193
+ }
1194
+ throw error;
1195
+ }
1196
+ const routeFiles = [];
1197
+ const ignore = options.ignore || ["node_modules", ".git"];
1198
+ async function scanDirectory(dir) {
1199
+ const entries = await fs3.readdir(dir, { withFileTypes: true });
1200
+ for (const entry of entries) {
1201
+ const fullPath = path3.join(dir, entry.name);
1202
+ if (entry.isDirectory() && ignore.includes(entry.name)) {
1203
+ continue;
1204
+ }
1205
+ if (entry.isDirectory()) {
1206
+ await scanDirectory(fullPath);
1207
+ } else if (isRouteFile(entry.name)) {
1208
+ routeFiles.push(fullPath);
1209
+ }
1210
+ }
1184
1211
  }
1185
- };
1186
-
1187
- // src/context/store.ts
1188
- var import_node_async_hooks = require("async_hooks");
1189
- var contextStorage = new import_node_async_hooks.AsyncLocalStorage();
1190
- function runWithContext(context, callback) {
1191
- return contextStorage.run(context, callback);
1212
+ await scanDirectory(absoluteDir);
1213
+ return routeFiles;
1214
+ }
1215
+ function isRouteFile(filename) {
1216
+ return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
1192
1217
  }
1193
1218
 
1194
- // src/context/create.ts
1195
- var CONTENT_TYPE_HEADER = "Content-Type";
1196
- function parseRequestUrl(req) {
1197
- const originalUrl = req.url || "/";
1198
- const host = req.headers.host || "localhost";
1199
- const protocol = req.socket && req.socket.encrypted ? "https" : "http";
1200
- const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
1219
+ // src/router/discovery/loader.ts
1220
+ async function dynamicImport(filePath) {
1221
+ return import(filePath);
1222
+ }
1223
+ async function loadRouteModule(filePath, basePath) {
1201
1224
  try {
1202
- const url = new URL(fullUrl);
1203
- const path5 = url.pathname;
1204
- const query = {};
1205
- url.searchParams.forEach((value, key) => {
1206
- if (query[key] !== void 0) {
1207
- if (Array.isArray(query[key])) {
1208
- query[key].push(value);
1209
- } else {
1210
- query[key] = [query[key], value];
1211
- }
1212
- } else {
1213
- query[key] = value;
1225
+ const parsedRoute = parseRoutePath(filePath, basePath);
1226
+ console.log("parsedRoute:", parsedRoute);
1227
+ const module2 = await dynamicImport(filePath);
1228
+ console.log("Module exports:", Object.keys(module2));
1229
+ const routes = [];
1230
+ if (module2.default && typeof module2.default === "object") {
1231
+ console.log("Found default export:", module2.default);
1232
+ const route = {
1233
+ ...module2.default,
1234
+ path: parsedRoute.routePath
1235
+ };
1236
+ routes.push(route);
1237
+ }
1238
+ Object.entries(module2).forEach(([exportName, exportValue]) => {
1239
+ if (exportName === "default" || !exportValue || typeof exportValue !== "object") {
1240
+ return;
1241
+ }
1242
+ const potentialRoute = exportValue;
1243
+ if (isValidRoute(potentialRoute)) {
1244
+ console.log(`Found named route export: ${exportName}`, potentialRoute);
1245
+ const route = {
1246
+ ...potentialRoute,
1247
+ // Use the route's own path if it has one, otherwise derive from file
1248
+ path: parsedRoute.routePath
1249
+ };
1250
+ routes.push(route);
1214
1251
  }
1215
1252
  });
1216
- return { path: path5, url, query };
1253
+ if (routes.length === 0) {
1254
+ console.warn(`Route file ${filePath} does not export any valid route definitions`);
1255
+ return [];
1256
+ }
1257
+ console.log(`Loaded ${routes.length} route(s) from ${filePath}`);
1258
+ return routes;
1217
1259
  } catch (error) {
1218
- console.warn(`Invalid URL: ${fullUrl}`, error);
1219
- throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
1260
+ console.error(`Failed to load route module ${filePath}:`, error);
1261
+ return [];
1220
1262
  }
1221
1263
  }
1222
- function isHttp2Request(req) {
1223
- return "stream" in req || "httpVersionMajor" in req && req.httpVersionMajor === 2;
1264
+ function isValidRoute(obj) {
1265
+ if (!obj || typeof obj !== "object") {
1266
+ return false;
1267
+ }
1268
+ const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
1269
+ const hasHttpMethod = httpMethods.some(
1270
+ (method) => obj[method] && typeof obj[method] === "object" && obj[method].handler
1271
+ );
1272
+ return hasHttpMethod;
1224
1273
  }
1225
- function getProtocol(req) {
1226
- const encrypted = req.socket && req.socket.encrypted;
1227
- const forwardedProto = req.headers["x-forwarded-proto"];
1228
- if (forwardedProto) {
1229
- if (Array.isArray(forwardedProto)) {
1230
- return forwardedProto[0]?.split(",")[0]?.trim() || "http";
1231
- } else {
1232
- return forwardedProto.split(",")[0]?.trim() || "http";
1274
+
1275
+ // src/router/discovery/index.ts
1276
+ async function findRoutes(routesDir, options = {}) {
1277
+ const routeFiles = await findRouteFiles(routesDir, {
1278
+ ignore: options.ignore
1279
+ });
1280
+ const routes = [];
1281
+ for (const filePath of routeFiles) {
1282
+ const moduleRoutes = await loadRouteModule(filePath, routesDir);
1283
+ if (moduleRoutes.length > 0) {
1284
+ routes.push(...moduleRoutes);
1233
1285
  }
1234
1286
  }
1235
- return encrypted ? "https" : "http";
1287
+ return routes;
1236
1288
  }
1237
- async function createContext(req, res, options = {}) {
1238
- const { path: path5, url, query } = parseRequestUrl(req);
1239
- const method = req.method || "GET";
1240
- const isHttp2 = isHttp2Request(req);
1241
- const protocol = getProtocol(req);
1242
- const params = {};
1243
- const state = { ...options.initialState || {} };
1244
- const responseState = { sent: false };
1245
- const ctx = {
1246
- request: createRequestObject(req, {
1247
- path: path5,
1248
- url,
1249
- query,
1250
- params,
1251
- method,
1252
- isHttp2,
1253
- protocol
1254
- }),
1255
- response: {},
1256
- state
1257
- };
1258
- ctx.response = createResponseObject(res, responseState, ctx);
1259
- if (options.parseBody) {
1260
- await parseBodyIfNeeded(req, ctx);
1289
+
1290
+ // src/router/discovery/watchers.ts
1291
+ var path4 = __toESM(require("path"), 1);
1292
+ var import_chokidar = require("chokidar");
1293
+ function watchRoutes(routesDir, options = {}) {
1294
+ const routesByPath = /* @__PURE__ */ new Map();
1295
+ async function loadInitialRoutes() {
1296
+ try {
1297
+ const files = await findRouteFiles(routesDir, {
1298
+ ignore: options.ignore
1299
+ });
1300
+ for (const filePath of files) {
1301
+ await loadAndNotify(filePath);
1302
+ }
1303
+ } catch (error) {
1304
+ handleError(error);
1305
+ }
1261
1306
  }
1262
- return ctx;
1263
- }
1264
- function createRequestObject(req, info) {
1265
- return {
1266
- raw: req,
1267
- ...info,
1268
- header: createRequestHeaderGetter(req),
1269
- headers: createRequestHeadersGetter(req),
1270
- body: void 0
1271
- };
1272
- }
1273
- function createRequestHeaderGetter(req) {
1274
- return (name) => {
1275
- const value = req.headers[name.toLowerCase()];
1276
- if (Array.isArray(value)) {
1277
- return value.join(", ");
1307
+ async function loadAndNotify(filePath) {
1308
+ try {
1309
+ const routes = await loadRouteModule(filePath, routesDir);
1310
+ if (!routes || routes.length === 0) {
1311
+ return;
1312
+ }
1313
+ const existingRoutes = routesByPath.get(filePath);
1314
+ if (existingRoutes) {
1315
+ routesByPath.set(filePath, routes);
1316
+ if (options.onRouteChanged) {
1317
+ options.onRouteChanged(routes);
1318
+ }
1319
+ } else {
1320
+ routesByPath.set(filePath, routes);
1321
+ if (options.onRouteAdded) {
1322
+ options.onRouteAdded(routes);
1323
+ }
1324
+ }
1325
+ } catch (error) {
1326
+ handleError(error);
1278
1327
  }
1279
- return value || void 0;
1280
- };
1281
- }
1282
- function createRequestHeadersGetter(req) {
1283
- const headerGetter = createRequestHeaderGetter(req);
1284
- return (names) => {
1285
- if (names && Array.isArray(names) && names.length > 0) {
1286
- return names.reduce((acc, name) => {
1287
- acc[name] = headerGetter(name);
1288
- return acc;
1289
- }, {});
1328
+ }
1329
+ function handleRemoved(filePath) {
1330
+ const normalizedPath = path4.normalize(filePath);
1331
+ const routes = routesByPath.get(normalizedPath);
1332
+ if (routes && routes.length > 0 && options.onRouteRemoved) {
1333
+ options.onRouteRemoved(normalizedPath, routes);
1334
+ }
1335
+ routesByPath.delete(normalizedPath);
1336
+ }
1337
+ function handleError(error) {
1338
+ if (options.onError && error instanceof Error) {
1339
+ options.onError(error);
1290
1340
  } else {
1291
- return Object.entries(req.headers).reduce(
1292
- (acc, [key, value]) => {
1293
- acc[key] = Array.isArray(value) ? value.join(", ") : value || void 0;
1294
- return acc;
1295
- },
1296
- {}
1297
- );
1341
+ console.error("Route watcher error:", error);
1298
1342
  }
1299
- };
1300
- }
1301
- function createResponseObject(res, responseState, ctx) {
1302
- return {
1303
- raw: res,
1304
- get sent() {
1305
- return responseState.sent;
1306
- },
1307
- status: createStatusSetter(res, responseState, ctx),
1308
- header: createHeaderSetter(res, responseState, ctx),
1309
- headers: createHeadersSetter(res, responseState, ctx),
1310
- type: createContentTypeSetter(res, responseState, ctx),
1311
- json: createJsonResponder(res, responseState),
1312
- text: createTextResponder(res, responseState),
1313
- html: createHtmlResponder(res, responseState),
1314
- redirect: createRedirectResponder(res, responseState),
1315
- stream: createStreamResponder(res, responseState)
1316
- };
1317
- }
1318
- function createStatusSetter(res, responseState, ctx) {
1319
- return function statusSetter(code) {
1320
- if (responseState.sent) {
1321
- throw new ResponseSentError();
1343
+ }
1344
+ const watcher = (0, import_chokidar.watch)(routesDir, {
1345
+ ignored: [
1346
+ /(^|[/\\])\../,
1347
+ // Ignore dot files
1348
+ /node_modules/,
1349
+ ...options.ignore || []
1350
+ ],
1351
+ persistent: true,
1352
+ ignoreInitial: false,
1353
+ awaitWriteFinish: {
1354
+ stabilityThreshold: 300,
1355
+ pollInterval: 100
1322
1356
  }
1323
- res.statusCode = code;
1324
- return ctx.response;
1357
+ });
1358
+ watcher.on("add", loadAndNotify).on("change", loadAndNotify).on("unlink", handleRemoved).on("error", handleError);
1359
+ loadInitialRoutes().catch(handleError);
1360
+ return {
1361
+ /**
1362
+ * Close the watcher
1363
+ */
1364
+ close: () => watcher.close(),
1365
+ /**
1366
+ * Get all currently loaded routes (flattened)
1367
+ */
1368
+ getRoutes: () => {
1369
+ const allRoutes = [];
1370
+ for (const routes of routesByPath.values()) {
1371
+ allRoutes.push(...routes);
1372
+ }
1373
+ return allRoutes;
1374
+ },
1375
+ /**
1376
+ * Get routes organized by file path
1377
+ */
1378
+ getRoutesByFile: () => new Map(routesByPath)
1325
1379
  };
1326
1380
  }
1327
- function createHeaderSetter(res, responseState, ctx) {
1328
- return function headerSetter(name, value) {
1329
- if (responseState.sent) {
1330
- throw new ResponseSentHeaderError();
1331
- }
1332
- res.setHeader(name, value);
1333
- return ctx.response;
1381
+
1382
+ // src/router/handlers/error.ts
1383
+ function handleRouteError(ctx, error, options = {}) {
1384
+ if (options.log) {
1385
+ console.error("Route error:", error);
1386
+ }
1387
+ const status = getErrorStatus(error);
1388
+ const response = {
1389
+ error: getErrorType(error),
1390
+ message: getErrorMessage(error)
1334
1391
  };
1335
- }
1336
- function createHeadersSetter(res, responseState, ctx) {
1337
- return function headersSetter(headers) {
1338
- if (responseState.sent) {
1339
- throw new ResponseSentHeaderError();
1392
+ if (options.detailed) {
1393
+ if (error instanceof Error) {
1394
+ response.stack = error.stack;
1340
1395
  }
1341
- for (const [name, value] of Object.entries(headers)) {
1342
- res.setHeader(name, value);
1396
+ if (error && typeof error === "object" && "details" in error && error.details) {
1397
+ response.details = error.details;
1343
1398
  }
1344
- return ctx.response;
1345
- };
1399
+ }
1400
+ ctx.response.status(status).json(response);
1346
1401
  }
1347
- function createContentTypeSetter(res, responseState, ctx) {
1348
- return function typeSetter(type) {
1349
- if (responseState.sent) {
1350
- throw new ResponseSentContentError();
1402
+ function getErrorStatus(error) {
1403
+ if (error && typeof error === "object") {
1404
+ if ("status" in error && typeof error.status === "number") {
1405
+ return error.status;
1351
1406
  }
1352
- res.setHeader(CONTENT_TYPE_HEADER, type);
1353
- return ctx.response;
1354
- };
1355
- }
1356
- function createJsonResponder(res, responseState) {
1357
- return function jsonResponder(body, status) {
1358
- if (responseState.sent) {
1359
- throw new ResponseSentError();
1407
+ if ("statusCode" in error && typeof error.statusCode === "number") {
1408
+ return error.statusCode;
1360
1409
  }
1361
- if (status !== void 0) {
1362
- res.statusCode = status;
1410
+ if ("code" in error && typeof error.code === "string") {
1411
+ return getStatusFromCode(error.code);
1363
1412
  }
1364
- res.setHeader(CONTENT_TYPE_HEADER, "application/json");
1365
- res.end(JSON.stringify(body));
1366
- responseState.sent = true;
1367
- };
1413
+ }
1414
+ return 500;
1368
1415
  }
1369
- function createTextResponder(res, responseState) {
1370
- return function textResponder(body, status) {
1371
- if (responseState.sent) {
1372
- throw new ResponseSentError();
1373
- }
1374
- if (status !== void 0) {
1375
- res.statusCode = status;
1376
- }
1377
- res.setHeader(CONTENT_TYPE_HEADER, "text/plain");
1378
- res.end(body);
1379
- responseState.sent = true;
1380
- };
1416
+ function getStatusFromCode(code) {
1417
+ switch (code) {
1418
+ case "NOT_FOUND":
1419
+ return 404;
1420
+ case "UNAUTHORIZED":
1421
+ return 401;
1422
+ case "FORBIDDEN":
1423
+ return 403;
1424
+ case "BAD_REQUEST":
1425
+ return 400;
1426
+ case "CONFLICT":
1427
+ return 409;
1428
+ default:
1429
+ return 500;
1430
+ }
1381
1431
  }
1382
- function createHtmlResponder(res, responseState) {
1383
- return function htmlResponder(body, status) {
1384
- if (responseState.sent) {
1385
- throw new ResponseSentError();
1432
+ function getErrorType(error) {
1433
+ if (error && typeof error === "object") {
1434
+ if ("type" in error && typeof error.type === "string") {
1435
+ return error.type;
1386
1436
  }
1387
- if (status !== void 0) {
1388
- res.statusCode = status;
1437
+ if ("name" in error && typeof error.name === "string") {
1438
+ return error.name;
1389
1439
  }
1390
- res.setHeader(CONTENT_TYPE_HEADER, "text/html");
1391
- res.end(body);
1392
- responseState.sent = true;
1393
- };
1394
- }
1395
- function createRedirectResponder(res, responseState) {
1396
- return function redirectResponder(url, status = 302) {
1397
- if (responseState.sent) {
1398
- throw new ResponseSentError();
1440
+ if (error instanceof Error) {
1441
+ return error.constructor.name;
1399
1442
  }
1400
- res.statusCode = status;
1401
- res.setHeader("Location", url);
1402
- res.end();
1403
- responseState.sent = true;
1404
- };
1443
+ }
1444
+ return "Error";
1405
1445
  }
1406
- function createStreamResponder(res, responseState) {
1407
- return function streamResponder(readable, options = {}) {
1408
- if (responseState.sent) {
1409
- throw new ResponseSentError();
1446
+ function getErrorMessage(error) {
1447
+ if (error instanceof Error) {
1448
+ return error.message;
1449
+ }
1450
+ if (error && typeof error === "object") {
1451
+ if ("message" in error && typeof error.message === "string") {
1452
+ return error.message;
1410
1453
  }
1411
- if (options.status !== void 0) {
1412
- res.statusCode = options.status;
1454
+ }
1455
+ return String(error);
1456
+ }
1457
+
1458
+ // src/router/validation/body.ts
1459
+ var import_zod2 = require("zod");
1460
+ function validateBody(body, schema) {
1461
+ if (schema instanceof import_zod2.z.ZodObject) {
1462
+ return schema.strict().parse(body);
1463
+ }
1464
+ return schema.parse(body);
1465
+ }
1466
+
1467
+ // src/router/validation/params.ts
1468
+ var import_zod3 = require("zod");
1469
+ function validateParams(params, schema) {
1470
+ if (schema instanceof import_zod3.z.ZodObject) {
1471
+ return schema.strict().parse(params);
1472
+ }
1473
+ return schema.parse(params);
1474
+ }
1475
+
1476
+ // src/router/validation/query.ts
1477
+ var import_zod4 = require("zod");
1478
+ function validateQuery(query, schema) {
1479
+ if (schema instanceof import_zod4.z.ZodObject) {
1480
+ return schema.strict().parse(query);
1481
+ }
1482
+ return schema.parse(query);
1483
+ }
1484
+
1485
+ // src/router/validation/response.ts
1486
+ var import_zod5 = require("zod");
1487
+ function validateResponse(response, schema) {
1488
+ if (schema instanceof import_zod5.z.ZodObject) {
1489
+ return schema.strict().parse(response);
1490
+ }
1491
+ return schema.parse(response);
1492
+ }
1493
+
1494
+ // src/router/validation/schema.ts
1495
+ function createRequestValidator(schema, debug = false) {
1496
+ const middlewareFn = async (ctx, next) => {
1497
+ const errors = {};
1498
+ if (schema.params && ctx.request.params) {
1499
+ try {
1500
+ ctx.request.params = validateParams(ctx.request.params, schema.params);
1501
+ } catch (error) {
1502
+ errors.params = formatValidationError(error);
1503
+ }
1413
1504
  }
1414
- if (options.contentType) {
1415
- res.setHeader(CONTENT_TYPE_HEADER, options.contentType);
1505
+ if (schema.query && ctx.request.query) {
1506
+ try {
1507
+ ctx.request.query = validateQuery(ctx.request.query, schema.query);
1508
+ } catch (error) {
1509
+ errors.query = formatValidationError(error);
1510
+ }
1416
1511
  }
1417
- if (options.headers) {
1418
- for (const [name, value] of Object.entries(options.headers)) {
1419
- res.setHeader(name, value);
1512
+ if (schema.body) {
1513
+ try {
1514
+ ctx.request.body = validateBody(ctx.request.body, schema.body);
1515
+ } catch (error) {
1516
+ errors.body = formatValidationError(error);
1420
1517
  }
1421
1518
  }
1422
- readable.pipe(res);
1423
- readable.on("end", () => {
1424
- responseState.sent = true;
1425
- });
1426
- readable.on("error", (err) => {
1427
- console.error("Stream error:", err);
1428
- if (!responseState.sent) {
1429
- res.statusCode = 500;
1430
- res.end("Stream error");
1431
- responseState.sent = true;
1519
+ if (Object.keys(errors).length > 0) {
1520
+ ctx.response.status(400).json({
1521
+ error: "Validation Error",
1522
+ details: errors
1523
+ });
1524
+ return;
1525
+ }
1526
+ await next();
1527
+ };
1528
+ return {
1529
+ name: "RequestValidator",
1530
+ execute: middlewareFn,
1531
+ debug
1532
+ };
1533
+ }
1534
+ function createResponseValidator(responseSchema, debug = false) {
1535
+ const middlewareFn = async (ctx, next) => {
1536
+ const originalJson = ctx.response.json;
1537
+ ctx.response.json = (body, status) => {
1538
+ try {
1539
+ const validatedBody = validateResponse(body, responseSchema);
1540
+ ctx.response.json = originalJson;
1541
+ return originalJson.call(ctx.response, validatedBody, status);
1542
+ } catch (error) {
1543
+ ctx.response.json = originalJson;
1544
+ console.error("Response validation error:", error);
1545
+ ctx.response.status(500).json({
1546
+ error: "Internal Server Error",
1547
+ message: "Response validation failed"
1548
+ });
1549
+ return ctx.response;
1432
1550
  }
1433
- });
1551
+ };
1552
+ await next();
1553
+ };
1554
+ return {
1555
+ name: "ResponseValidator",
1556
+ execute: middlewareFn,
1557
+ debug
1434
1558
  };
1435
1559
  }
1436
- async function parseBodyIfNeeded(req, ctx) {
1437
- if (shouldSkipParsing(req.method)) {
1438
- return;
1439
- }
1440
- const contentType = req.headers["content-type"] || "";
1441
- const contentLength = parseInt(req.headers["content-length"] || "0", 10);
1442
- if (contentLength === 0 || contentLength > 1048576) {
1443
- return;
1444
- }
1445
- try {
1446
- await parseBodyByContentType(req, ctx, contentType);
1447
- } catch (error) {
1448
- setBodyError(ctx, "body_read_error", "Error reading request body", error);
1449
- }
1450
- }
1451
- function shouldSkipParsing(method) {
1452
- const skipMethods = ["GET", "HEAD", "OPTIONS"];
1453
- return skipMethods.includes(method || "GET");
1454
- }
1455
- async function parseBodyByContentType(req, ctx, contentType) {
1456
- if (contentType.includes("application/json")) {
1457
- await parseJsonBody(req, ctx);
1458
- } else if (contentType.includes("application/x-www-form-urlencoded")) {
1459
- await parseFormUrlEncodedBody(req, ctx);
1460
- } else if (contentType.includes("text/")) {
1461
- await parseTextBody(req, ctx);
1462
- }
1463
- }
1464
- async function parseJsonBody(req, ctx) {
1465
- const body = await readRequestBody(req);
1466
- if (!body) {
1467
- console.warn("Empty body, skipping JSON parsing");
1468
- return;
1469
- }
1470
- if (body.trim() === "null") {
1471
- console.warn('Body is the string "null"');
1472
- ctx.request.body = null;
1473
- return;
1474
- }
1475
- try {
1476
- const json = JSON.parse(body);
1477
- ctx.request.body = json;
1478
- } catch (error) {
1479
- ctx.request.body = null;
1480
- setBodyError(ctx, "json_parse_error", "Invalid JSON in request body", error);
1560
+ function formatValidationError(error) {
1561
+ if (error && typeof error === "object" && "format" in error && typeof error.format === "function") {
1562
+ return error.format();
1481
1563
  }
1564
+ return error instanceof Error ? error.message : String(error);
1482
1565
  }
1483
- async function parseFormUrlEncodedBody(req, ctx) {
1484
- const body = await readRequestBody(req);
1485
- if (!body) return;
1486
- try {
1487
- ctx.request.body = parseUrlEncodedData(body);
1488
- } catch (error) {
1489
- ctx.request.body = null;
1490
- setBodyError(ctx, "form_parse_error", "Invalid form data in request body", error);
1566
+
1567
+ // src/router/handlers/executor.ts
1568
+ async function executeHandler(ctx, routeOptions, params) {
1569
+ const middleware = [...routeOptions.middleware || []];
1570
+ if (routeOptions.schema) {
1571
+ if (routeOptions.schema.params || routeOptions.schema.query || routeOptions.schema.body) {
1572
+ middleware.unshift(createRequestValidator(routeOptions.schema));
1573
+ }
1574
+ if (routeOptions.schema.response) {
1575
+ middleware.push(createResponseValidator(routeOptions.schema.response));
1576
+ }
1491
1577
  }
1492
- }
1493
- function parseUrlEncodedData(body) {
1494
- const params = new URLSearchParams(body);
1495
- const formData = {};
1496
- params.forEach((value, key) => {
1497
- if (formData[key] !== void 0) {
1498
- if (Array.isArray(formData[key])) {
1499
- formData[key].push(value);
1500
- } else {
1501
- formData[key] = [formData[key], value];
1502
- }
1503
- } else {
1504
- formData[key] = value;
1578
+ const handler = compose([...middleware]);
1579
+ await handler(ctx, async () => {
1580
+ const result = await routeOptions.handler(ctx, params);
1581
+ if (!ctx.response.sent && result !== void 0) {
1582
+ ctx.response.json(result);
1505
1583
  }
1506
1584
  });
1507
- return formData;
1508
1585
  }
1509
- async function parseTextBody(req, ctx) {
1510
- const body = await readRequestBody(req);
1511
- if (body) {
1512
- ctx.request.body = body;
1586
+
1587
+ // src/router/matching/params.ts
1588
+ function extractParams(path5, pattern, paramNames) {
1589
+ const match = pattern.exec(path5);
1590
+ if (!match) {
1591
+ return {};
1513
1592
  }
1593
+ const params = {};
1594
+ for (let i = 0; i < paramNames.length; i++) {
1595
+ params[paramNames[i]] = match[i + 1] || "";
1596
+ }
1597
+ return params;
1514
1598
  }
1515
- function setBodyError(ctx, type, message, error) {
1516
- ctx.state._bodyError = { type, message, error };
1517
- }
1518
- async function readRequestBody(req) {
1519
- return new Promise((resolve2, reject) => {
1520
- const chunks = [];
1521
- req.on("data", (chunk) => {
1522
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1523
- });
1524
- req.on("end", () => {
1525
- resolve2(Buffer.concat(chunks).toString("utf8"));
1526
- });
1527
- req.on("error", (err) => {
1528
- reject(err);
1529
- });
1599
+ function compilePathPattern(path5) {
1600
+ const paramNames = [];
1601
+ if (path5 === "/") {
1602
+ return {
1603
+ pattern: /^\/$/,
1604
+ paramNames: []
1605
+ };
1606
+ }
1607
+ let patternString = path5.replace(/([.+*?^$(){}|\\])/g, "\\$1");
1608
+ patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
1609
+ paramNames.push(paramName);
1610
+ return "/([^/]+)";
1611
+ }).replace(/\/\[([^\]]+)\]/g, (_, paramName) => {
1612
+ paramNames.push(paramName);
1613
+ return "/([^/]+)";
1530
1614
  });
1615
+ patternString = `${patternString}(?:/)?`;
1616
+ const pattern = new RegExp(`^${patternString}$`);
1617
+ return {
1618
+ pattern,
1619
+ paramNames
1620
+ };
1531
1621
  }
1532
1622
 
1533
- // src/server/request-handler.ts
1534
- function createRequestHandler(serverInstance) {
1535
- return async (req, res) => {
1536
- try {
1537
- const context = await createContext(req, res, {
1538
- parseBody: true
1539
- // Enable automatic body parsing
1540
- });
1541
- const handler = compose(serverInstance.middleware);
1542
- await runWithContext(context, async () => {
1543
- try {
1544
- await handler(context, async () => {
1545
- if (!context.response.sent) {
1546
- await serverInstance.router.handleRequest(context);
1547
- if (!context.response.sent) {
1548
- context.response.status(404).json({
1549
- error: "Not Found",
1550
- message: `Route not found: ${context.request.method} ${context.request.path}`
1551
- });
1552
- }
1553
- }
1554
- });
1555
- } catch (error) {
1556
- console.error("Error processing request:", error);
1557
- if (!context.response.sent) {
1558
- context.response.json(
1559
- {
1560
- error: "Internal Server Error",
1561
- message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
1562
- },
1563
- 500
1564
- );
1565
- }
1623
+ // src/router/matching/matcher.ts
1624
+ function createMatcher() {
1625
+ const routes = [];
1626
+ return {
1627
+ /**
1628
+ * Add a route to the matcher
1629
+ */
1630
+ add(path5, method, routeOptions) {
1631
+ const { pattern, paramNames } = compilePathPattern(path5);
1632
+ const newRoute = {
1633
+ path: path5,
1634
+ method,
1635
+ pattern,
1636
+ paramNames,
1637
+ routeOptions
1638
+ };
1639
+ const insertIndex = routes.findIndex((route) => paramNames.length < route.paramNames.length);
1640
+ if (insertIndex === -1) {
1641
+ routes.push(newRoute);
1642
+ } else {
1643
+ routes.splice(insertIndex, 0, newRoute);
1644
+ }
1645
+ },
1646
+ /**
1647
+ * Match a URL path to a route
1648
+ */
1649
+ match(path5, method) {
1650
+ const pathname = path5.split("?")[0];
1651
+ if (!pathname) return null;
1652
+ for (const route of routes) {
1653
+ if (route.method !== method) continue;
1654
+ const match = route.pattern.exec(pathname);
1655
+ if (match) {
1656
+ const params = extractParams(path5, route.pattern, route.paramNames);
1657
+ return {
1658
+ route: route.routeOptions,
1659
+ params
1660
+ };
1566
1661
  }
1567
- });
1568
- } catch (error) {
1569
- console.error("Error creating context:", error);
1570
- res.writeHead(500, { "Content-Type": "application/json" });
1571
- res.end(
1572
- JSON.stringify({
1573
- error: "Internal Server Error",
1574
- message: "Failed to process request"
1575
- })
1662
+ }
1663
+ const matchingPath = routes.find(
1664
+ (route) => route.method !== method && route.pattern.test(path5)
1576
1665
  );
1666
+ if (matchingPath) {
1667
+ return {
1668
+ route: null,
1669
+ params: {},
1670
+ methodNotAllowed: true,
1671
+ allowedMethods: routes.filter((route) => route.pattern.test(path5)).map((route) => route.method)
1672
+ };
1673
+ }
1674
+ return null;
1675
+ },
1676
+ /**
1677
+ * Get all registered routes
1678
+ */
1679
+ getRoutes() {
1680
+ return routes.map((route) => ({
1681
+ path: route.path,
1682
+ method: route.method
1683
+ }));
1684
+ },
1685
+ /**
1686
+ * Find routes matching a specific path
1687
+ */
1688
+ findRoutes(path5) {
1689
+ return routes.filter((route) => route.pattern.test(path5)).map((route) => ({
1690
+ path: route.path,
1691
+ method: route.method,
1692
+ params: extractParams(path5, route.pattern, route.paramNames)
1693
+ }));
1577
1694
  }
1578
1695
  };
1579
1696
  }
1580
1697
 
1581
- // src/server/start.ts
1582
- async function prepareCertificates(http2Options) {
1583
- if (!http2Options.enabled) {
1584
- return {};
1585
- }
1586
- const { keyFile, certFile } = http2Options;
1587
- const isDevMode = process.env.NODE_ENV === "development";
1588
- const certificatesMissing = !keyFile || !certFile;
1589
- if (certificatesMissing && isDevMode) {
1590
- const devCerts = await generateDevCertificates();
1591
- return devCerts;
1592
- }
1593
- if (certificatesMissing) {
1594
- throw new Error(
1595
- "HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically."
1596
- );
1597
- }
1598
- return { keyFile, certFile };
1599
- }
1600
- function createServerInstance(isHttp2, certOptions) {
1601
- if (!isHttp2) {
1602
- return http.createServer();
1603
- }
1604
- const http2ServerOptions = {
1605
- allowHTTP1: true
1606
- // Allow fallback to HTTP/1.1
1698
+ // src/router/router.ts
1699
+ var DEFAULT_ROUTER_OPTIONS = {
1700
+ routesDir: "./routes",
1701
+ basePath: "/",
1702
+ watchMode: process.env.NODE_ENV === "development"
1703
+ };
1704
+ function createRouter(options) {
1705
+ const routerOptions = {
1706
+ ...DEFAULT_ROUTER_OPTIONS,
1707
+ ...options
1607
1708
  };
1608
- try {
1609
- if (certOptions.keyFile) {
1610
- http2ServerOptions.key = fs3.readFileSync(certOptions.keyFile);
1611
- }
1612
- if (certOptions.certFile) {
1613
- http2ServerOptions.cert = fs3.readFileSync(certOptions.certFile);
1614
- }
1615
- } catch (err) {
1616
- throw new Error(
1617
- `Failed to read certificate files: ${err instanceof Error ? err.message : String(err)}`
1618
- );
1709
+ if (options.basePath && !options.basePath.startsWith("/")) {
1710
+ console.warn("Base path does nothing");
1619
1711
  }
1620
- return http2.createSecureServer(http2ServerOptions);
1621
- }
1622
- function listenOnPort(server, port, host, isHttp2) {
1623
- return new Promise((resolve2, reject) => {
1624
- server.listen(port, host, () => {
1625
- const protocol = isHttp2 ? "https" : "http";
1626
- const url = `${protocol}://${host}:${port}`;
1627
- console.log(`
1628
- \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}
1629
-
1630
- \u26A1 BlaizeJS DEVELOPMENT SERVER HOT AND READY \u26A1
1631
-
1632
- \u{1F680} Server: ${url}
1633
- \u{1F525} Hot Reload: Enabled
1634
- \u{1F6E0}\uFE0F Mode: Development
1635
-
1636
- Time to build something amazing! \u{1F680}
1637
-
1638
- \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}
1639
- `);
1640
- resolve2();
1641
- });
1642
- server.on("error", (err) => {
1643
- console.error("Server error:", err);
1644
- reject(err);
1645
- });
1646
- });
1647
- }
1648
- async function initializePlugins(serverInstance) {
1649
- for (const plugin of serverInstance.plugins) {
1650
- if (typeof plugin.initialize === "function") {
1651
- await plugin.initialize(serverInstance);
1712
+ const routes = [];
1713
+ const matcher = createMatcher();
1714
+ let initialized = false;
1715
+ let initializationPromise = null;
1716
+ let _watchers = null;
1717
+ const routeSources = /* @__PURE__ */ new Map();
1718
+ const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
1719
+ function addRouteWithSource(route, source) {
1720
+ const existingSources = routeSources.get(route.path) || [];
1721
+ if (existingSources.includes(source)) {
1722
+ console.warn(`Skipping duplicate route: ${route.path} from ${source}`);
1723
+ return;
1652
1724
  }
1653
- }
1654
- }
1655
- async function startServer(serverInstance, serverOptions) {
1656
- if (serverInstance.server) {
1657
- return;
1658
- }
1659
- try {
1660
- const port = serverOptions.port;
1661
- const host = serverOptions.host;
1662
- await initializePlugins(serverInstance);
1663
- const http2Options = serverOptions.http2 || { enabled: true };
1664
- const isHttp2 = !!http2Options.enabled;
1665
- const certOptions = await prepareCertificates(http2Options);
1666
- if (serverOptions.http2 && certOptions.keyFile && certOptions.certFile) {
1667
- serverOptions.http2.keyFile = certOptions.keyFile;
1668
- serverOptions.http2.certFile = certOptions.certFile;
1725
+ if (existingSources.length > 0) {
1726
+ const conflictError = new Error(
1727
+ `Route conflict for path "${route.path}": already defined in ${existingSources.join(", ")}, now being added from ${source}`
1728
+ );
1729
+ console.error(conflictError.message);
1730
+ throw conflictError;
1669
1731
  }
1670
- const server = createServerInstance(isHttp2, certOptions);
1671
- serverInstance.server = server;
1672
- serverInstance.port = port;
1673
- serverInstance.host = host;
1674
- const requestHandler = createRequestHandler(serverInstance);
1675
- server.on("request", requestHandler);
1676
- await listenOnPort(server, port, host, isHttp2);
1677
- } catch (error) {
1678
- console.error("Failed to start server:", error);
1679
- throw error;
1680
- }
1681
- }
1682
-
1683
- // src/server/stop.ts
1684
- async function stopServer(serverInstance, options = {}) {
1685
- const server = serverInstance.server;
1686
- const events = serverInstance.events;
1687
- if (!server) {
1688
- return;
1732
+ routeSources.set(route.path, [...existingSources, source]);
1733
+ addRouteInternal(route);
1689
1734
  }
1690
- const timeout = options.timeout || 3e4;
1691
- try {
1692
- if (options.onStopping) {
1693
- await options.onStopping();
1694
- }
1695
- events.emit("stopping");
1696
- await serverInstance.pluginManager.onServerStop(serverInstance, server);
1697
- const timeoutPromise = new Promise((_, reject) => {
1698
- setTimeout(() => {
1699
- reject(new Error("Server shutdown timed out waiting for requests to complete"));
1700
- }, timeout);
1701
- });
1702
- const closePromise = new Promise((resolve2, reject) => {
1703
- server.close((err) => {
1704
- if (err) {
1705
- return reject(err);
1706
- }
1707
- resolve2();
1735
+ async function loadRoutesFromDirectory(directory, source, prefix) {
1736
+ try {
1737
+ const discoveredRoutes = await findRoutes(directory, {
1738
+ basePath: routerOptions.basePath
1708
1739
  });
1709
- });
1710
- await Promise.race([closePromise, timeoutPromise]);
1711
- await serverInstance.pluginManager.terminatePlugins(serverInstance);
1712
- if (options.onStopped) {
1713
- await options.onStopped();
1740
+ for (const route of discoveredRoutes) {
1741
+ const finalRoute = prefix ? {
1742
+ ...route,
1743
+ path: `${prefix}${route.path}`
1744
+ } : route;
1745
+ addRouteWithSource(finalRoute, source);
1746
+ }
1747
+ console.log(
1748
+ `Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
1749
+ );
1750
+ } catch (error) {
1751
+ console.error(`Failed to load routes from ${source}:`, error);
1752
+ throw error;
1714
1753
  }
1715
- events.emit("stopped");
1716
- serverInstance.server = null;
1717
- } catch (error) {
1718
- events.emit("error", error);
1719
- throw error;
1720
1754
  }
1721
- }
1722
- function registerSignalHandlers(stopFn) {
1723
- const sigintHandler = () => stopFn().catch(console.error);
1724
- const sigtermHandler = () => stopFn().catch(console.error);
1725
- process.on("SIGINT", sigintHandler);
1726
- process.on("SIGTERM", sigtermHandler);
1727
- return {
1728
- unregister: () => {
1729
- process.removeListener("SIGINT", sigintHandler);
1730
- process.removeListener("SIGTERM", sigtermHandler);
1755
+ async function initialize() {
1756
+ if (initialized || initializationPromise) {
1757
+ return initializationPromise;
1731
1758
  }
1732
- };
1733
- }
1734
-
1735
- // src/server/validation.ts
1736
- var import_zod5 = require("zod");
1737
- var middlewareSchema = import_zod5.z.custom(
1738
- (data) => data !== null && typeof data === "object" && "execute" in data && typeof data.execute === "function",
1739
- {
1740
- message: "Expected middleware to have an execute function"
1741
- }
1742
- );
1743
- var pluginSchema = import_zod5.z.custom(
1744
- (data) => data !== null && typeof data === "object" && "register" in data && typeof data.register === "function",
1745
- {
1746
- message: "Expected a valid plugin object with a register method"
1759
+ initializationPromise = (async () => {
1760
+ try {
1761
+ for (const directory of routeDirectories) {
1762
+ await loadRoutesFromDirectory(directory, directory);
1763
+ }
1764
+ if (routerOptions.watchMode) {
1765
+ setupWatcherForAllDirectories();
1766
+ }
1767
+ initialized = true;
1768
+ } catch (error) {
1769
+ console.error("Failed to initialize router:", error);
1770
+ throw error;
1771
+ }
1772
+ })();
1773
+ return initializationPromise;
1747
1774
  }
1748
- );
1749
- var http2Schema = import_zod5.z.object({
1750
- enabled: import_zod5.z.boolean().optional().default(true),
1751
- keyFile: import_zod5.z.string().optional(),
1752
- certFile: import_zod5.z.string().optional()
1753
- }).refine(
1754
- (data) => {
1755
- if (data.enabled && process.env.NODE_ENV === "production") {
1756
- return data.keyFile && data.certFile;
1757
- }
1758
- return true;
1759
- },
1760
- {
1761
- message: "When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"
1775
+ function addRouteInternal(route) {
1776
+ routes.push(route);
1777
+ Object.entries(route).forEach(([method, methodOptions]) => {
1778
+ if (method === "path" || !methodOptions) return;
1779
+ matcher.add(route.path, method, methodOptions);
1780
+ });
1762
1781
  }
1763
- );
1764
- var serverOptionsSchema = import_zod5.z.object({
1765
- port: import_zod5.z.number().int().positive().optional().default(3e3),
1766
- host: import_zod5.z.string().optional().default("localhost"),
1767
- routesDir: import_zod5.z.string().optional().default("./routes"),
1768
- http2: http2Schema.optional().default({
1769
- enabled: true
1770
- }),
1771
- middleware: import_zod5.z.array(middlewareSchema).optional().default([]),
1772
- plugins: import_zod5.z.array(pluginSchema).optional().default([])
1773
- });
1774
- function validateServerOptions(options) {
1775
- try {
1776
- return serverOptionsSchema.parse(options);
1777
- } catch (error) {
1778
- if (error instanceof import_zod5.z.ZodError) {
1779
- const formattedError = error.format();
1780
- throw new Error(`Invalid server options: ${JSON.stringify(formattedError, null, 2)}`);
1781
- }
1782
- throw new Error(`Invalid server options: ${String(error)}`);
1782
+ function createWatcherCallbacks(directory, source, prefix) {
1783
+ return {
1784
+ onRouteAdded: (addedRoutes) => {
1785
+ console.log(
1786
+ `${addedRoutes.length} route(s) added from ${directory}:`,
1787
+ addedRoutes.map((r) => r.path)
1788
+ );
1789
+ addedRoutes.forEach((route) => {
1790
+ const finalRoute = prefix ? { ...route, path: `${prefix}${route.path}` } : route;
1791
+ addRouteWithSource(finalRoute, source);
1792
+ });
1793
+ },
1794
+ onRouteChanged: (changedRoutes) => {
1795
+ console.log(
1796
+ `${changedRoutes.length} route(s) changed in ${directory}:`,
1797
+ changedRoutes.map((r) => r.path)
1798
+ );
1799
+ changedRoutes.forEach((route) => {
1800
+ const finalPath = prefix ? `${prefix}${route.path}` : route.path;
1801
+ const index = routes.findIndex((r) => r.path === finalPath);
1802
+ if (index >= 0) {
1803
+ routes.splice(index, 1);
1804
+ const sources = routeSources.get(finalPath) || [];
1805
+ const filteredSources = sources.filter((s) => s !== source);
1806
+ if (filteredSources.length > 0) {
1807
+ routeSources.set(finalPath, filteredSources);
1808
+ } else {
1809
+ routeSources.delete(finalPath);
1810
+ }
1811
+ }
1812
+ const finalRoute = prefix ? { ...route, path: finalPath } : route;
1813
+ addRouteWithSource(finalRoute, source);
1814
+ });
1815
+ },
1816
+ onRouteRemoved: (filePath, removedRoutes) => {
1817
+ console.log(
1818
+ `File removed from ${directory}: ${filePath} with ${removedRoutes.length} route(s):`,
1819
+ removedRoutes.map((r) => r.path)
1820
+ );
1821
+ removedRoutes.forEach((route) => {
1822
+ const finalPath = prefix ? `${prefix}${route.path}` : route.path;
1823
+ const index = routes.findIndex((r) => r.path === finalPath);
1824
+ if (index >= 0) {
1825
+ routes.splice(index, 1);
1826
+ }
1827
+ const sources = routeSources.get(finalPath) || [];
1828
+ const filteredSources = sources.filter((s) => s !== source);
1829
+ if (filteredSources.length > 0) {
1830
+ routeSources.set(finalPath, filteredSources);
1831
+ } else {
1832
+ routeSources.delete(finalPath);
1833
+ }
1834
+ });
1835
+ },
1836
+ onError: (error) => {
1837
+ console.error(`Route watcher error for ${directory}:`, error);
1838
+ }
1839
+ };
1783
1840
  }
1784
- }
1785
-
1786
- // src/plugins/lifecycle.ts
1787
- function createPluginLifecycleManager(options = {}) {
1788
- const { continueOnError = true, debug = false, onError } = options;
1789
- function log(message, ...args) {
1790
- if (debug) {
1791
- console.log(`[PluginLifecycle] ${message}`, ...args);
1841
+ function setupWatcherForDirectory(directory, source, prefix) {
1842
+ const callbacks = createWatcherCallbacks(directory, source, prefix);
1843
+ const watcher = watchRoutes(directory, {
1844
+ ignore: ["node_modules", ".git"],
1845
+ ...callbacks
1846
+ });
1847
+ if (!_watchers) {
1848
+ _watchers = /* @__PURE__ */ new Map();
1792
1849
  }
1850
+ _watchers.set(directory, watcher);
1851
+ return watcher;
1793
1852
  }
1794
- function handleError(plugin, phase, error) {
1795
- const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
1796
- if (onError) {
1797
- onError(plugin, phase, error);
1798
- } else {
1799
- console.error(errorMessage, error);
1800
- }
1801
- if (!continueOnError) {
1802
- throw new Error(errorMessage);
1853
+ function setupWatcherForAllDirectories() {
1854
+ for (const directory of routeDirectories) {
1855
+ setupWatcherForDirectory(directory, directory);
1803
1856
  }
1804
1857
  }
1858
+ initialize().catch((error) => {
1859
+ console.error("Failed to initialize router on creation:", error);
1860
+ });
1805
1861
  return {
1806
1862
  /**
1807
- * Initialize all plugins
1863
+ * Handle an incoming request
1808
1864
  */
1809
- async initializePlugins(server) {
1810
- log("Initializing plugins...");
1811
- for (const plugin of server.plugins) {
1812
- if (plugin.initialize) {
1813
- try {
1814
- log(`Initializing plugin: ${plugin.name}`);
1815
- await plugin.initialize(server);
1816
- } catch (error) {
1817
- handleError(plugin, "initialize", error);
1818
- }
1865
+ async handleRequest(ctx) {
1866
+ if (!initialized) {
1867
+ await initialize();
1868
+ }
1869
+ const { method, path: path5 } = ctx.request;
1870
+ const match = matcher.match(path5, method);
1871
+ if (!match) {
1872
+ ctx.response.status(404).json({ error: "Not Found" });
1873
+ return;
1874
+ }
1875
+ if (match.methodNotAllowed) {
1876
+ ctx.response.status(405).json({
1877
+ error: "Method Not Allowed",
1878
+ allowed: match.allowedMethods
1879
+ });
1880
+ if (match.allowedMethods && match.allowedMethods.length > 0) {
1881
+ ctx.response.header("Allow", match.allowedMethods.join(", "));
1819
1882
  }
1883
+ return;
1884
+ }
1885
+ ctx.request.params = match.params;
1886
+ try {
1887
+ await executeHandler(ctx, match.route, match.params);
1888
+ } catch (error) {
1889
+ handleRouteError(ctx, error, {
1890
+ detailed: process.env.NODE_ENV !== "production",
1891
+ log: true
1892
+ });
1820
1893
  }
1821
- log(`Initialized ${server.plugins.length} plugins`);
1822
1894
  },
1823
1895
  /**
1824
- * Terminate all plugins in reverse order
1896
+ * Get all registered routes
1825
1897
  */
1826
- async terminatePlugins(server) {
1827
- log("Terminating plugins...");
1828
- const pluginsToTerminate = [...server.plugins].reverse();
1829
- for (const plugin of pluginsToTerminate) {
1830
- if (plugin.terminate) {
1831
- try {
1832
- log(`Terminating plugin: ${plugin.name}`);
1833
- await plugin.terminate(server);
1834
- } catch (error) {
1835
- handleError(plugin, "terminate", error);
1836
- }
1837
- }
1838
- }
1839
- log(`Terminated ${pluginsToTerminate.length} plugins`);
1898
+ getRoutes() {
1899
+ return [...routes];
1840
1900
  },
1841
1901
  /**
1842
- * Notify plugins that the server has started
1902
+ * Add a route programmatically
1843
1903
  */
1844
- async onServerStart(server, httpServer) {
1845
- log("Notifying plugins of server start...");
1846
- for (const plugin of server.plugins) {
1847
- if (plugin.onServerStart) {
1848
- try {
1849
- log(`Notifying plugin of server start: ${plugin.name}`);
1850
- await plugin.onServerStart(httpServer);
1851
- } catch (error) {
1852
- handleError(plugin, "onServerStart", error);
1853
- }
1904
+ addRoute(route) {
1905
+ addRouteInternal(route);
1906
+ },
1907
+ /**
1908
+ * Add a route directory (for plugins)
1909
+ */
1910
+ async addRouteDirectory(directory, options2 = {}) {
1911
+ if (routeDirectories.has(directory)) {
1912
+ console.warn(`Route directory ${directory} already registered`);
1913
+ return;
1914
+ }
1915
+ routeDirectories.add(directory);
1916
+ if (initialized) {
1917
+ await loadRoutesFromDirectory(directory, directory, options2.prefix);
1918
+ if (routerOptions.watchMode) {
1919
+ setupWatcherForDirectory(directory, directory, options2.prefix);
1854
1920
  }
1855
1921
  }
1856
1922
  },
1857
1923
  /**
1858
- * Notify plugins that the server is stopping
1924
+ * Get route conflicts
1859
1925
  */
1860
- async onServerStop(server, httpServer) {
1861
- log("Notifying plugins of server stop...");
1862
- const pluginsToNotify = [...server.plugins].reverse();
1863
- for (const plugin of pluginsToNotify) {
1864
- if (plugin.onServerStop) {
1865
- try {
1866
- log(`Notifying plugin of server stop: ${plugin.name}`);
1867
- await plugin.onServerStop(httpServer);
1868
- } catch (error) {
1869
- handleError(plugin, "onServerStop", error);
1870
- }
1926
+ getRouteConflicts() {
1927
+ const conflicts = [];
1928
+ for (const [path5, sources] of routeSources.entries()) {
1929
+ if (sources.length > 1) {
1930
+ conflicts.push({ path: path5, sources });
1871
1931
  }
1872
1932
  }
1933
+ return conflicts;
1873
1934
  }
1874
1935
  };
1875
1936
  }
1876
1937
 
1877
- // src/plugins/errors.ts
1878
- var PluginValidationError = class extends Error {
1879
- constructor(pluginName, message) {
1880
- super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
1881
- this.pluginName = pluginName;
1882
- this.name = "PluginValidationError";
1883
- }
1884
- };
1885
-
1886
- // src/plugins/validation.ts
1887
- var RESERVED_NAMES = /* @__PURE__ */ new Set([
1888
- "core",
1889
- "server",
1890
- "router",
1891
- "middleware",
1892
- "context",
1893
- "blaize",
1894
- "blaizejs"
1895
- ]);
1896
- var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
1897
- var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
1898
- function validatePlugin(plugin, options = {}) {
1899
- const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
1900
- if (!plugin || typeof plugin !== "object") {
1901
- throw new PluginValidationError("", "Plugin must be an object");
1902
- }
1903
- const p = plugin;
1904
- if (!p.name || typeof p.name !== "string") {
1905
- throw new PluginValidationError("", "Plugin must have a name (string)");
1906
- }
1907
- if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
1908
- throw new PluginValidationError(
1909
- p.name,
1910
- "Plugin name must be lowercase letters, numbers, and hyphens only"
1911
- );
1912
- }
1913
- if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
1914
- throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
1915
- }
1916
- if (requireVersion) {
1917
- if (!p.version || typeof p.version !== "string") {
1918
- throw new PluginValidationError(p.name, "Plugin must have a version (string)");
1919
- }
1920
- if (!VALID_VERSION_PATTERN.test(p.version)) {
1921
- throw new PluginValidationError(
1922
- p.name,
1923
- 'Plugin version must follow semantic versioning (e.g., "1.0.0")'
1924
- );
1925
- }
1926
- }
1927
- if (!p.register || typeof p.register !== "function") {
1928
- throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
1929
- }
1930
- const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
1931
- for (const method of lifecycleMethods) {
1932
- if (p[method] && typeof p[method] !== "function") {
1933
- throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
1934
- }
1935
- }
1936
- }
1937
-
1938
1938
  // src/server/create.ts
1939
1939
  var DEFAULT_OPTIONS = {
1940
1940
  port: 3e3,