blaizejs 0.2.0 → 0.2.2

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