blaizejs 0.2.0 → 0.2.3

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