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