blaizejs 0.3.2 → 0.3.4
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/chunk-DTDGIBMA.js +11 -0
- package/dist/{chunk-IFP53BNM.js.map → chunk-DTDGIBMA.js.map} +1 -1
- package/dist/{unsupported-media-type-error-VVHRDTUH.js → chunk-EE2VJ6JY.js} +3 -9
- package/dist/{chunk-3VK325MM.js.map → chunk-EE2VJ6JY.js.map} +1 -1
- package/dist/chunk-HSLLYUVO.js +11 -0
- package/dist/{chunk-CQKM74J4.js.map → chunk-HSLLYUVO.js.map} +1 -1
- package/dist/chunk-TL4GIFTB.js +11 -0
- package/dist/{chunk-HB6MRTGD.js.map → chunk-TL4GIFTB.js.map} +1 -1
- package/dist/chunk-VLVWNGUO.js +11 -0
- package/dist/{chunk-7IM52S7P.js.map → chunk-VLVWNGUO.js.map} +1 -1
- package/dist/index.cjs +12 -3486
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12 -3153
- package/dist/index.js.map +1 -1
- package/dist/{validation-error-TXMSFWZL.js → internal-server-error-GWBNT3OO.js} +3 -9
- package/dist/{internal-server-error-PVME2DGN.js → payload-too-large-error-EBM5BNWG.js} +3 -9
- package/dist/{payload-too-large-error-QQG7MKGT.js → unsupported-media-type-error-YQ7GCZ32.js} +3 -9
- package/dist/validation-error-6JDCGV2S.js +11 -0
- package/package.json +5 -5
- package/dist/chunk-3VK325MM.js +0 -31
- package/dist/chunk-7IM52S7P.js +0 -31
- package/dist/chunk-CQKM74J4.js +0 -39
- package/dist/chunk-HB6MRTGD.js +0 -39
- package/dist/chunk-IFP53BNM.js +0 -149
- /package/dist/{internal-server-error-PVME2DGN.js.map → internal-server-error-GWBNT3OO.js.map} +0 -0
- /package/dist/{payload-too-large-error-QQG7MKGT.js.map → payload-too-large-error-EBM5BNWG.js.map} +0 -0
- /package/dist/{unsupported-media-type-error-VVHRDTUH.js.map → unsupported-media-type-error-YQ7GCZ32.js.map} +0 -0
- /package/dist/{validation-error-TXMSFWZL.js.map → validation-error-6JDCGV2S.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,3172 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
* blaizejs v0.3.
|
|
3
|
+
* blaizejs v0.3.4
|
|
4
4
|
* 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.
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) 2025 BlaizeJS Contributors
|
|
7
7
|
* @license MIT
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
} from "./
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./chunk-HB6MRTGD.js";
|
|
16
|
-
import {
|
|
17
|
-
PayloadTooLargeError
|
|
18
|
-
} from "./chunk-3VK325MM.js";
|
|
19
|
-
import {
|
|
20
|
-
UnsupportedMediaTypeError
|
|
21
|
-
} from "./chunk-7IM52S7P.js";
|
|
22
|
-
import {
|
|
23
|
-
BlaizeError,
|
|
24
|
-
ErrorSeverity,
|
|
25
|
-
ErrorType,
|
|
26
|
-
__require,
|
|
27
|
-
generateCorrelationId,
|
|
28
|
-
getCurrentCorrelationId,
|
|
29
|
-
isBodyParseError
|
|
30
|
-
} from "./chunk-IFP53BNM.js";
|
|
31
|
-
|
|
32
|
-
// src/middleware/execute.ts
|
|
33
|
-
function execute(middleware, ctx, next) {
|
|
34
|
-
if (!middleware) {
|
|
35
|
-
return Promise.resolve(next());
|
|
36
|
-
}
|
|
37
|
-
if (middleware.skip && middleware.skip(ctx)) {
|
|
38
|
-
return Promise.resolve(next());
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const result = middleware.execute(ctx, next);
|
|
42
|
-
if (result instanceof Promise) {
|
|
43
|
-
return result;
|
|
44
|
-
} else {
|
|
45
|
-
return Promise.resolve(result);
|
|
46
|
-
}
|
|
47
|
-
} catch (error) {
|
|
48
|
-
return Promise.reject(error);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/middleware/compose.ts
|
|
53
|
-
function compose(middlewareStack) {
|
|
54
|
-
if (middlewareStack.length === 0) {
|
|
55
|
-
return async (_, next) => {
|
|
56
|
-
await Promise.resolve(next());
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
return async function(ctx, finalHandler) {
|
|
60
|
-
const called = /* @__PURE__ */ new Set();
|
|
61
|
-
const dispatch = async (i) => {
|
|
62
|
-
if (i >= middlewareStack.length) {
|
|
63
|
-
return Promise.resolve(finalHandler());
|
|
64
|
-
}
|
|
65
|
-
const middleware = middlewareStack[i];
|
|
66
|
-
const nextDispatch = () => {
|
|
67
|
-
if (called.has(i)) {
|
|
68
|
-
throw new Error("next() called multiple times");
|
|
69
|
-
}
|
|
70
|
-
called.add(i);
|
|
71
|
-
return dispatch(i + 1);
|
|
72
|
-
};
|
|
73
|
-
return execute(middleware, ctx, nextDispatch);
|
|
74
|
-
};
|
|
75
|
-
return dispatch(0);
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/middleware/create.ts
|
|
80
|
-
function create(handlerOrOptions) {
|
|
81
|
-
if (typeof handlerOrOptions === "function") {
|
|
82
|
-
return {
|
|
83
|
-
name: "anonymous",
|
|
84
|
-
// Default name for function middleware
|
|
85
|
-
execute: handlerOrOptions,
|
|
86
|
-
debug: false
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
const { name = "anonymous", handler, skip, debug = false } = handlerOrOptions;
|
|
90
|
-
const middleware = {
|
|
91
|
-
name,
|
|
92
|
-
execute: handler,
|
|
93
|
-
debug
|
|
94
|
-
};
|
|
95
|
-
if (skip !== void 0) {
|
|
96
|
-
return {
|
|
97
|
-
...middleware,
|
|
98
|
-
skip
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return middleware;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/plugins/create.ts
|
|
105
|
-
function create2(name, version, setup, defaultOptions = {}) {
|
|
106
|
-
if (!name || typeof name !== "string") {
|
|
107
|
-
throw new Error("Plugin name must be a non-empty string");
|
|
108
|
-
}
|
|
109
|
-
if (!version || typeof version !== "string") {
|
|
110
|
-
throw new Error("Plugin version must be a non-empty string");
|
|
111
|
-
}
|
|
112
|
-
if (typeof setup !== "function") {
|
|
113
|
-
throw new Error("Plugin setup must be a function");
|
|
114
|
-
}
|
|
115
|
-
return function pluginFactory(userOptions) {
|
|
116
|
-
const mergedOptions = { ...defaultOptions, ...userOptions };
|
|
117
|
-
const plugin = {
|
|
118
|
-
name,
|
|
119
|
-
version,
|
|
120
|
-
// The register hook calls the user's setup function
|
|
121
|
-
register: async (app) => {
|
|
122
|
-
const result = await setup(app, mergedOptions);
|
|
123
|
-
if (result && typeof result === "object") {
|
|
124
|
-
Object.assign(plugin, result);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
return plugin;
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// src/router/create.ts
|
|
133
|
-
import { fileURLToPath } from "node:url";
|
|
134
|
-
|
|
135
|
-
// src/config.ts
|
|
136
|
-
var config = {};
|
|
137
|
-
function setRuntimeConfig(newConfig) {
|
|
138
|
-
config = { ...config, ...newConfig };
|
|
139
|
-
}
|
|
140
|
-
function getRoutesDir() {
|
|
141
|
-
if (!config.routesDir) {
|
|
142
|
-
throw new Error("Routes directory not configured. Make sure server is properly initialized.");
|
|
143
|
-
}
|
|
144
|
-
return config.routesDir;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// src/router/discovery/parser.ts
|
|
148
|
-
import * as path from "node:path";
|
|
149
|
-
function parseRoutePath(filePath, basePath) {
|
|
150
|
-
if (filePath.startsWith("file://")) {
|
|
151
|
-
filePath = filePath.replace("file://", "");
|
|
152
|
-
}
|
|
153
|
-
if (basePath.startsWith("file://")) {
|
|
154
|
-
basePath = basePath.replace("file://", "");
|
|
155
|
-
}
|
|
156
|
-
const forwardSlashFilePath = filePath.replace(/\\/g, "/");
|
|
157
|
-
const forwardSlashBasePath = basePath.replace(/\\/g, "/");
|
|
158
|
-
const normalizedBasePath = forwardSlashBasePath.endsWith("/") ? forwardSlashBasePath : `${forwardSlashBasePath}/`;
|
|
159
|
-
let relativePath = forwardSlashFilePath;
|
|
160
|
-
if (forwardSlashFilePath.startsWith(normalizedBasePath)) {
|
|
161
|
-
relativePath = forwardSlashFilePath.substring(normalizedBasePath.length);
|
|
162
|
-
} else if (forwardSlashFilePath.startsWith(forwardSlashBasePath)) {
|
|
163
|
-
relativePath = forwardSlashFilePath.substring(forwardSlashBasePath.length);
|
|
164
|
-
if (relativePath.startsWith("/")) {
|
|
165
|
-
relativePath = relativePath.substring(1);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
relativePath = path.relative(forwardSlashBasePath, forwardSlashFilePath).replace(/\\/g, "/");
|
|
169
|
-
}
|
|
170
|
-
relativePath = relativePath.replace(/\.[^.]+$/, "");
|
|
171
|
-
const segments = relativePath.split("/").filter(Boolean);
|
|
172
|
-
const params = [];
|
|
173
|
-
const routeSegments = segments.map((segment) => {
|
|
174
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
175
|
-
const paramName = segment.slice(1, -1);
|
|
176
|
-
params.push(paramName);
|
|
177
|
-
return `:${paramName}`;
|
|
178
|
-
}
|
|
179
|
-
return segment;
|
|
180
|
-
});
|
|
181
|
-
let routePath = routeSegments.length > 0 ? `/${routeSegments.join("/")}` : "/";
|
|
182
|
-
if (routePath.endsWith("/index")) {
|
|
183
|
-
routePath = routePath.slice(0, -6) || "/";
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
filePath,
|
|
187
|
-
routePath,
|
|
188
|
-
params
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/router/create.ts
|
|
193
|
-
function getCallerFilePath() {
|
|
194
|
-
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
195
|
-
try {
|
|
196
|
-
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
197
|
-
const stack = new Error().stack;
|
|
198
|
-
const callerFrame = stack[3];
|
|
199
|
-
if (!callerFrame || typeof callerFrame.getFileName !== "function") {
|
|
200
|
-
throw new Error("Unable to determine caller file frame");
|
|
201
|
-
}
|
|
202
|
-
const fileName = callerFrame.getFileName();
|
|
203
|
-
if (!fileName) {
|
|
204
|
-
throw new Error("Unable to determine caller file name");
|
|
205
|
-
}
|
|
206
|
-
if (fileName.startsWith("file://")) {
|
|
207
|
-
return fileURLToPath(fileName);
|
|
208
|
-
}
|
|
209
|
-
return fileName;
|
|
210
|
-
} finally {
|
|
211
|
-
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function getRoutePath() {
|
|
215
|
-
const callerPath = getCallerFilePath();
|
|
216
|
-
const routesDir = getRoutesDir();
|
|
217
|
-
const parsedRoute = parseRoutePath(callerPath, routesDir);
|
|
218
|
-
console.log(`\u{1F50E} Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
|
|
219
|
-
return parsedRoute.routePath;
|
|
220
|
-
}
|
|
221
|
-
var createGetRoute = (config2) => {
|
|
222
|
-
validateMethodConfig("GET", config2);
|
|
223
|
-
const path6 = getRoutePath();
|
|
224
|
-
return {
|
|
225
|
-
GET: config2,
|
|
226
|
-
// Let TypeScript infer the proper types
|
|
227
|
-
path: path6
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
var createPostRoute = (config2) => {
|
|
231
|
-
validateMethodConfig("POST", config2);
|
|
232
|
-
const path6 = getRoutePath();
|
|
233
|
-
return {
|
|
234
|
-
POST: config2,
|
|
235
|
-
// Let TypeScript infer the proper types
|
|
236
|
-
path: path6
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
var createPutRoute = (config2) => {
|
|
240
|
-
validateMethodConfig("PUT", config2);
|
|
241
|
-
const path6 = getRoutePath();
|
|
242
|
-
return {
|
|
243
|
-
PUT: config2,
|
|
244
|
-
// Let TypeScript infer the proper types
|
|
245
|
-
path: path6
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
var createDeleteRoute = (config2) => {
|
|
249
|
-
validateMethodConfig("DELETE", config2);
|
|
250
|
-
const path6 = getRoutePath();
|
|
251
|
-
return {
|
|
252
|
-
DELETE: config2,
|
|
253
|
-
// Let TypeScript infer the proper types
|
|
254
|
-
path: path6
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
var createPatchRoute = (config2) => {
|
|
258
|
-
validateMethodConfig("PATCH", config2);
|
|
259
|
-
const path6 = getRoutePath();
|
|
260
|
-
return {
|
|
261
|
-
PATCH: config2,
|
|
262
|
-
// Let TypeScript infer the proper types
|
|
263
|
-
path: path6
|
|
264
|
-
};
|
|
265
|
-
};
|
|
266
|
-
var createHeadRoute = (config2) => {
|
|
267
|
-
validateMethodConfig("HEAD", config2);
|
|
268
|
-
const path6 = getRoutePath();
|
|
269
|
-
return {
|
|
270
|
-
HEAD: config2,
|
|
271
|
-
// Let TypeScript infer the proper types
|
|
272
|
-
path: path6
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
var createOptionsRoute = (config2) => {
|
|
276
|
-
validateMethodConfig("OPTIONS", config2);
|
|
277
|
-
const path6 = getRoutePath();
|
|
278
|
-
return {
|
|
279
|
-
OPTIONS: config2,
|
|
280
|
-
// Let TypeScript infer the proper types
|
|
281
|
-
path: path6
|
|
282
|
-
};
|
|
283
|
-
};
|
|
284
|
-
function validateMethodConfig(method, config2) {
|
|
285
|
-
if (!config2.handler || typeof config2.handler !== "function") {
|
|
286
|
-
throw new Error(`Handler for method ${method} must be a function`);
|
|
287
|
-
}
|
|
288
|
-
if (config2.middleware && !Array.isArray(config2.middleware)) {
|
|
289
|
-
throw new Error(`Middleware for method ${method} must be an array`);
|
|
290
|
-
}
|
|
291
|
-
if (config2.schema) {
|
|
292
|
-
validateSchema(method, config2.schema);
|
|
293
|
-
}
|
|
294
|
-
switch (method) {
|
|
295
|
-
case "GET":
|
|
296
|
-
case "HEAD":
|
|
297
|
-
case "DELETE":
|
|
298
|
-
if (config2.schema?.body) {
|
|
299
|
-
console.warn(`Warning: ${method} requests typically don't have request bodies`);
|
|
300
|
-
}
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function validateSchema(method, schema) {
|
|
305
|
-
const { params, query, body, response } = schema;
|
|
306
|
-
if (params && (!params._def || typeof params.parse !== "function")) {
|
|
307
|
-
throw new Error(`Params schema for ${method} must be a valid Zod schema`);
|
|
308
|
-
}
|
|
309
|
-
if (query && (!query._def || typeof query.parse !== "function")) {
|
|
310
|
-
throw new Error(`Query schema for ${method} must be a valid Zod schema`);
|
|
311
|
-
}
|
|
312
|
-
if (body && (!body._def || typeof body.parse !== "function")) {
|
|
313
|
-
throw new Error(`Body schema for ${method} must be a valid Zod schema`);
|
|
314
|
-
}
|
|
315
|
-
if (response && (!response._def || typeof response.parse !== "function")) {
|
|
316
|
-
throw new Error(`Response schema for ${method} must be a valid Zod schema`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// src/server/create.ts
|
|
321
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
322
|
-
import EventEmitter from "node:events";
|
|
323
|
-
|
|
324
|
-
// src/server/start.ts
|
|
325
|
-
import * as fs2 from "node:fs";
|
|
326
|
-
import * as http from "node:http";
|
|
327
|
-
import * as http2 from "node:http2";
|
|
328
|
-
|
|
329
|
-
// src/server/dev-certificate.ts
|
|
330
|
-
import * as fs from "node:fs";
|
|
331
|
-
import * as path2 from "node:path";
|
|
332
|
-
import * as selfsigned from "selfsigned";
|
|
333
|
-
async function generateDevCertificates() {
|
|
334
|
-
const certDir = path2.join(process.cwd(), ".blaizejs", "certs");
|
|
335
|
-
const keyPath = path2.join(certDir, "dev.key");
|
|
336
|
-
const certPath = path2.join(certDir, "dev.cert");
|
|
337
|
-
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
|
338
|
-
return {
|
|
339
|
-
keyFile: keyPath,
|
|
340
|
-
certFile: certPath
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
if (!fs.existsSync(certDir)) {
|
|
344
|
-
fs.mkdirSync(certDir, { recursive: true });
|
|
345
|
-
}
|
|
346
|
-
const attrs = [{ name: "commonName", value: "localhost" }];
|
|
347
|
-
const options = {
|
|
348
|
-
days: 365,
|
|
349
|
-
algorithm: "sha256",
|
|
350
|
-
keySize: 2048,
|
|
351
|
-
extensions: [
|
|
352
|
-
{ name: "basicConstraints", cA: true },
|
|
353
|
-
{
|
|
354
|
-
name: "keyUsage",
|
|
355
|
-
keyCertSign: true,
|
|
356
|
-
digitalSignature: true,
|
|
357
|
-
nonRepudiation: true,
|
|
358
|
-
keyEncipherment: true,
|
|
359
|
-
dataEncipherment: true
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
name: "extKeyUsage",
|
|
363
|
-
serverAuth: true,
|
|
364
|
-
clientAuth: true
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
name: "subjectAltName",
|
|
368
|
-
altNames: [
|
|
369
|
-
{ type: 2, value: "localhost" },
|
|
370
|
-
{ type: 7, ip: "127.0.0.1" }
|
|
371
|
-
]
|
|
372
|
-
}
|
|
373
|
-
]
|
|
374
|
-
};
|
|
375
|
-
const pems = selfsigned.generate(attrs, options);
|
|
376
|
-
fs.writeFileSync(keyPath, Buffer.from(pems.private, "utf-8"));
|
|
377
|
-
fs.writeFileSync(certPath, Buffer.from(pems.cert, "utf-8"));
|
|
378
|
-
console.log(`
|
|
379
|
-
\u{1F512} Generated self-signed certificates for development at ${certDir}
|
|
380
|
-
`);
|
|
381
|
-
return {
|
|
382
|
-
keyFile: keyPath,
|
|
383
|
-
certFile: certPath
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// src/context/errors.ts
|
|
388
|
-
var ResponseSentError = class extends Error {
|
|
389
|
-
constructor(message = "\u274C Response has already been sent") {
|
|
390
|
-
super(message);
|
|
391
|
-
this.name = "ResponseSentError";
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
var ResponseSentHeaderError = class extends ResponseSentError {
|
|
395
|
-
constructor(message = "Cannot set header after response has been sent") {
|
|
396
|
-
super(message);
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
var ResponseSentContentError = class extends ResponseSentError {
|
|
400
|
-
constructor(message = "Cannot set content type after response has been sent") {
|
|
401
|
-
super(message);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
var ParseUrlError = class extends ResponseSentError {
|
|
405
|
-
constructor(message = "Invalide URL") {
|
|
406
|
-
super(message);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// src/context/store.ts
|
|
411
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
412
|
-
var contextStorage = new AsyncLocalStorage();
|
|
413
|
-
function runWithContext(context, callback) {
|
|
414
|
-
return contextStorage.run(context, callback);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// src/upload/multipart-parser.ts
|
|
418
|
-
import { randomUUID } from "node:crypto";
|
|
419
|
-
import { createWriteStream } from "node:fs";
|
|
420
|
-
import { tmpdir } from "node:os";
|
|
421
|
-
import { join as join2 } from "node:path";
|
|
422
|
-
import { Readable } from "node:stream";
|
|
423
|
-
|
|
424
|
-
// src/upload/utils.ts
|
|
425
|
-
var BOUNDARY_REGEX = /boundary=([^;]+)/i;
|
|
426
|
-
var CONTENT_DISPOSITION_REGEX = /Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;[\s\r\n]*filename="([^"]*)")?/i;
|
|
427
|
-
var CONTENT_TYPE_REGEX = /Content-Type:\s*([^\r\n]+)/i;
|
|
428
|
-
var MULTIPART_REGEX = /multipart\/form-data/i;
|
|
429
|
-
function extractBoundary(contentType) {
|
|
430
|
-
const match = contentType.match(BOUNDARY_REGEX);
|
|
431
|
-
if (!match || !match[1]) return null;
|
|
432
|
-
let boundary = match[1].trim();
|
|
433
|
-
if (boundary.startsWith('"') && boundary.endsWith('"')) {
|
|
434
|
-
boundary = boundary.slice(1, -1);
|
|
435
|
-
}
|
|
436
|
-
return boundary || null;
|
|
437
|
-
}
|
|
438
|
-
function parseContentDisposition(headers) {
|
|
439
|
-
const match = headers.match(CONTENT_DISPOSITION_REGEX);
|
|
440
|
-
if (!match || !match[1]) return null;
|
|
441
|
-
return {
|
|
442
|
-
name: match[1],
|
|
443
|
-
filename: match[2] !== void 0 ? match[2] : void 0
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function parseContentType(headers) {
|
|
447
|
-
const match = headers.match(CONTENT_TYPE_REGEX);
|
|
448
|
-
return match && match[1]?.trim() ? match[1].trim() : "application/octet-stream";
|
|
449
|
-
}
|
|
450
|
-
function isMultipartContent(contentType) {
|
|
451
|
-
return MULTIPART_REGEX.test(contentType);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/upload/multipart-parser.ts
|
|
455
|
-
var DEFAULT_OPTIONS = {
|
|
456
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
457
|
-
// 10MB
|
|
458
|
-
maxFiles: 10,
|
|
459
|
-
maxFieldSize: 1 * 1024 * 1024,
|
|
460
|
-
// 1MB
|
|
461
|
-
allowedMimeTypes: [],
|
|
462
|
-
allowedExtensions: [],
|
|
463
|
-
strategy: "stream",
|
|
464
|
-
tempDir: tmpdir(),
|
|
465
|
-
computeHash: false
|
|
466
|
-
};
|
|
467
|
-
function createParserState(boundary, options = {}) {
|
|
468
|
-
return {
|
|
469
|
-
boundary: Buffer.from(`--${boundary}`),
|
|
470
|
-
options: { ...DEFAULT_OPTIONS, ...options },
|
|
471
|
-
fields: /* @__PURE__ */ new Map(),
|
|
472
|
-
files: /* @__PURE__ */ new Map(),
|
|
473
|
-
buffer: Buffer.alloc(0),
|
|
474
|
-
stage: "boundary",
|
|
475
|
-
currentHeaders: "",
|
|
476
|
-
currentField: null,
|
|
477
|
-
currentFilename: void 0,
|
|
478
|
-
currentMimetype: "application/octet-stream",
|
|
479
|
-
currentContentLength: 0,
|
|
480
|
-
fileCount: 0,
|
|
481
|
-
fieldCount: 0,
|
|
482
|
-
currentBufferChunks: [],
|
|
483
|
-
currentStream: null,
|
|
484
|
-
currentTempPath: null,
|
|
485
|
-
currentWriteStream: null,
|
|
486
|
-
streamController: null,
|
|
487
|
-
cleanupTasks: [],
|
|
488
|
-
// Track validation state
|
|
489
|
-
hasFoundValidBoundary: false,
|
|
490
|
-
hasProcessedAnyPart: false,
|
|
491
|
-
isFinished: false
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
async function processChunk(state, chunk) {
|
|
495
|
-
const newBuffer = Buffer.concat([state.buffer, chunk]);
|
|
496
|
-
let currentState = { ...state, buffer: newBuffer };
|
|
497
|
-
while (currentState.buffer.length > 0 && !currentState.isFinished) {
|
|
498
|
-
const nextState = await processCurrentStage(currentState);
|
|
499
|
-
if (nextState === currentState) break;
|
|
500
|
-
currentState = nextState;
|
|
501
|
-
}
|
|
502
|
-
return currentState;
|
|
503
|
-
}
|
|
504
|
-
async function processCurrentStage(state) {
|
|
505
|
-
switch (state.stage) {
|
|
506
|
-
case "boundary":
|
|
507
|
-
return processBoundary(state);
|
|
508
|
-
case "headers":
|
|
509
|
-
return processHeaders(state);
|
|
510
|
-
case "content":
|
|
511
|
-
return processContent(state);
|
|
512
|
-
default: {
|
|
513
|
-
const { InternalServerError: InternalServerError2 } = await import("./internal-server-error-PVME2DGN.js");
|
|
514
|
-
throw new InternalServerError2(`Invalid parser stage`, {
|
|
515
|
-
operation: state.stage
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function processBoundary(state) {
|
|
521
|
-
const boundaryIndex = state.buffer.indexOf(state.boundary);
|
|
522
|
-
if (boundaryIndex === -1) return state;
|
|
523
|
-
const hasFoundValidBoundary = true;
|
|
524
|
-
let buffer = state.buffer.subarray(boundaryIndex + state.boundary.length);
|
|
525
|
-
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("--"))) {
|
|
526
|
-
return {
|
|
527
|
-
...state,
|
|
528
|
-
buffer,
|
|
529
|
-
hasFoundValidBoundary,
|
|
530
|
-
isFinished: true,
|
|
531
|
-
stage: "boundary"
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("\r\n"))) {
|
|
535
|
-
buffer = buffer.subarray(2);
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
...state,
|
|
539
|
-
buffer,
|
|
540
|
-
hasFoundValidBoundary,
|
|
541
|
-
stage: "headers",
|
|
542
|
-
currentHeaders: ""
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
async function processHeaders(state) {
|
|
546
|
-
const headerEnd = state.buffer.indexOf("\r\n\r\n");
|
|
547
|
-
if (headerEnd === -1) return state;
|
|
548
|
-
const headers = state.buffer.subarray(0, headerEnd).toString("utf8");
|
|
549
|
-
const buffer = state.buffer.subarray(headerEnd + 4);
|
|
550
|
-
const disposition = parseContentDisposition(headers);
|
|
551
|
-
if (!disposition) {
|
|
552
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
553
|
-
throw new ValidationError2("Missing or invalid Content-Disposition header");
|
|
554
|
-
}
|
|
555
|
-
const mimetype = parseContentType(headers);
|
|
556
|
-
const isFile = disposition.filename !== void 0;
|
|
557
|
-
if (isFile && state.fileCount >= state.options.maxFiles) {
|
|
558
|
-
const { PayloadTooLargeError: PayloadTooLargeError2 } = await import("./payload-too-large-error-QQG7MKGT.js");
|
|
559
|
-
throw new PayloadTooLargeError2("Too many files in upload", {
|
|
560
|
-
fileCount: state.fileCount + 1,
|
|
561
|
-
maxFiles: state.options.maxFiles,
|
|
562
|
-
filename: disposition.filename
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
if (isFile && state.options.allowedMimeTypes.length > 0 && !state.options.allowedMimeTypes.includes(mimetype)) {
|
|
566
|
-
const { UnsupportedMediaTypeError: UnsupportedMediaTypeError2 } = await import("./unsupported-media-type-error-VVHRDTUH.js");
|
|
567
|
-
throw new UnsupportedMediaTypeError2("File type not allowed", {
|
|
568
|
-
receivedMimeType: mimetype,
|
|
569
|
-
allowedMimeTypes: state.options.allowedMimeTypes,
|
|
570
|
-
filename: disposition.filename
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
...state,
|
|
575
|
-
buffer,
|
|
576
|
-
stage: "content",
|
|
577
|
-
currentHeaders: headers,
|
|
578
|
-
currentField: disposition.name,
|
|
579
|
-
currentFilename: disposition.filename,
|
|
580
|
-
currentMimetype: mimetype,
|
|
581
|
-
currentContentLength: 0,
|
|
582
|
-
fileCount: isFile ? state.fileCount + 1 : state.fileCount,
|
|
583
|
-
fieldCount: isFile ? state.fieldCount : state.fieldCount + 1,
|
|
584
|
-
currentBufferChunks: []
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
async function processContent(state) {
|
|
588
|
-
const nextBoundaryIndex = state.buffer.indexOf(state.boundary);
|
|
589
|
-
let contentChunk;
|
|
590
|
-
let isComplete = false;
|
|
591
|
-
let buffer = state.buffer;
|
|
592
|
-
if (nextBoundaryIndex === -1) {
|
|
593
|
-
const safeLength = Math.max(0, state.buffer.length - state.boundary.length);
|
|
594
|
-
if (safeLength === 0) return state;
|
|
595
|
-
contentChunk = state.buffer.subarray(0, safeLength);
|
|
596
|
-
buffer = state.buffer.subarray(safeLength);
|
|
597
|
-
} else {
|
|
598
|
-
const contentEnd = Math.max(0, nextBoundaryIndex - 2);
|
|
599
|
-
contentChunk = state.buffer.subarray(0, contentEnd);
|
|
600
|
-
buffer = state.buffer.subarray(nextBoundaryIndex);
|
|
601
|
-
isComplete = true;
|
|
602
|
-
}
|
|
603
|
-
let updatedState = { ...state, buffer };
|
|
604
|
-
if (contentChunk.length > 0) {
|
|
605
|
-
updatedState = await processContentChunk(updatedState, contentChunk);
|
|
606
|
-
}
|
|
607
|
-
if (isComplete) {
|
|
608
|
-
updatedState = await finalizeCurrentPart(updatedState);
|
|
609
|
-
updatedState = {
|
|
610
|
-
...updatedState,
|
|
611
|
-
stage: "boundary",
|
|
612
|
-
hasProcessedAnyPart: true
|
|
613
|
-
// Mark that we've processed at least one part
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
return updatedState;
|
|
617
|
-
}
|
|
618
|
-
async function processContentChunk(state, chunk) {
|
|
619
|
-
const newContentLength = state.currentContentLength + chunk.length;
|
|
620
|
-
const maxSize = state.currentFilename !== void 0 ? state.options.maxFileSize : state.options.maxFieldSize;
|
|
621
|
-
if (newContentLength > maxSize) {
|
|
622
|
-
const isFile = state.currentFilename !== void 0;
|
|
623
|
-
const { PayloadTooLargeError: PayloadTooLargeError2 } = await import("./payload-too-large-error-QQG7MKGT.js");
|
|
624
|
-
const payloadErrorDetals = state.currentField ? {
|
|
625
|
-
contentType: isFile ? "file" : "field",
|
|
626
|
-
currentSize: newContentLength,
|
|
627
|
-
maxSize,
|
|
628
|
-
field: state.currentField,
|
|
629
|
-
filename: state.currentFilename
|
|
630
|
-
} : {
|
|
631
|
-
contentType: isFile ? "file" : "field",
|
|
632
|
-
currentSize: newContentLength,
|
|
633
|
-
maxSize,
|
|
634
|
-
filename: state.currentFilename
|
|
635
|
-
};
|
|
636
|
-
throw new PayloadTooLargeError2(
|
|
637
|
-
`${isFile ? "File" : "Field"} size exceeds limit`,
|
|
638
|
-
payloadErrorDetals
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
if (state.currentFilename !== void 0) {
|
|
642
|
-
return processFileChunk(state, chunk, newContentLength);
|
|
643
|
-
} else {
|
|
644
|
-
return {
|
|
645
|
-
...state,
|
|
646
|
-
currentContentLength: newContentLength,
|
|
647
|
-
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
async function processFileChunk(state, chunk, newContentLength) {
|
|
652
|
-
switch (state.options.strategy) {
|
|
653
|
-
case "memory":
|
|
654
|
-
return {
|
|
655
|
-
...state,
|
|
656
|
-
currentContentLength: newContentLength,
|
|
657
|
-
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
658
|
-
};
|
|
659
|
-
case "stream":
|
|
660
|
-
if (state.streamController) {
|
|
661
|
-
state.streamController.enqueue(chunk);
|
|
662
|
-
}
|
|
663
|
-
return { ...state, currentContentLength: newContentLength };
|
|
664
|
-
case "temp":
|
|
665
|
-
if (state.currentWriteStream) {
|
|
666
|
-
await writeToStream(state.currentWriteStream, chunk);
|
|
667
|
-
}
|
|
668
|
-
return { ...state, currentContentLength: newContentLength };
|
|
669
|
-
default: {
|
|
670
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
671
|
-
throw new ValidationError2(`Invalid parsing strategy`);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
async function initializeFileProcessing(state) {
|
|
676
|
-
if (state.currentFilename === void 0) return state;
|
|
677
|
-
switch (state.options.strategy) {
|
|
678
|
-
case "memory":
|
|
679
|
-
return { ...state, currentBufferChunks: [] };
|
|
680
|
-
case "stream": {
|
|
681
|
-
let streamController = null;
|
|
682
|
-
const stream = new ReadableStream({
|
|
683
|
-
start: (controller) => {
|
|
684
|
-
streamController = controller;
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
return {
|
|
688
|
-
...state,
|
|
689
|
-
currentStream: stream,
|
|
690
|
-
// Type cast for Node.js compatibility
|
|
691
|
-
streamController
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
case "temp": {
|
|
695
|
-
const tempPath = join2(state.options.tempDir, `upload-${randomUUID()}`);
|
|
696
|
-
const writeStream = createWriteStream(tempPath);
|
|
697
|
-
const cleanupTask = async () => {
|
|
698
|
-
try {
|
|
699
|
-
const { unlink } = await import("node:fs/promises");
|
|
700
|
-
await unlink(tempPath);
|
|
701
|
-
} catch (error) {
|
|
702
|
-
console.warn(`Failed to cleanup temp file: ${tempPath}`, error);
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
return {
|
|
706
|
-
...state,
|
|
707
|
-
currentTempPath: tempPath,
|
|
708
|
-
currentWriteStream: writeStream,
|
|
709
|
-
cleanupTasks: [...state.cleanupTasks, cleanupTask]
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
default: {
|
|
713
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
714
|
-
throw new ValidationError2(`Invalid file processing strategy`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
async function finalizeCurrentPart(state) {
|
|
719
|
-
if (!state.currentField) return resetCurrentPart(state);
|
|
720
|
-
if (state.currentFilename !== void 0) {
|
|
721
|
-
return finalizeFile(state);
|
|
722
|
-
} else {
|
|
723
|
-
return finalizeField(state);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
async function finalizeFile(state) {
|
|
727
|
-
if (!state.currentField || state.currentFilename === void 0) {
|
|
728
|
-
return resetCurrentPart(state);
|
|
729
|
-
}
|
|
730
|
-
let stream;
|
|
731
|
-
let buffer;
|
|
732
|
-
let tempPath;
|
|
733
|
-
switch (state.options.strategy) {
|
|
734
|
-
case "memory":
|
|
735
|
-
buffer = Buffer.concat(state.currentBufferChunks);
|
|
736
|
-
stream = Readable.from(buffer);
|
|
737
|
-
break;
|
|
738
|
-
case "stream":
|
|
739
|
-
if (state.streamController) {
|
|
740
|
-
state.streamController.close();
|
|
741
|
-
}
|
|
742
|
-
stream = state.currentStream;
|
|
743
|
-
break;
|
|
744
|
-
case "temp":
|
|
745
|
-
if (state.currentWriteStream) {
|
|
746
|
-
await closeStream(state.currentWriteStream);
|
|
747
|
-
}
|
|
748
|
-
tempPath = state.currentTempPath;
|
|
749
|
-
stream = Readable.from(Buffer.alloc(0));
|
|
750
|
-
break;
|
|
751
|
-
default: {
|
|
752
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
753
|
-
throw new ValidationError2(`Invalid file finalization strategy`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
const file = {
|
|
757
|
-
filename: state.currentFilename,
|
|
758
|
-
fieldname: state.currentField,
|
|
759
|
-
mimetype: state.currentMimetype,
|
|
760
|
-
size: state.currentContentLength,
|
|
761
|
-
stream,
|
|
762
|
-
buffer,
|
|
763
|
-
tempPath
|
|
764
|
-
};
|
|
765
|
-
const updatedFiles = addToCollection(state.files, state.currentField, file);
|
|
766
|
-
return {
|
|
767
|
-
...resetCurrentPart(state),
|
|
768
|
-
files: updatedFiles
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
function finalizeField(state) {
|
|
772
|
-
if (!state.currentField) return resetCurrentPart(state);
|
|
773
|
-
const value = Buffer.concat(state.currentBufferChunks).toString("utf8");
|
|
774
|
-
const updatedFields = addToCollection(state.fields, state.currentField, value);
|
|
775
|
-
return {
|
|
776
|
-
...resetCurrentPart(state),
|
|
777
|
-
fields: updatedFields
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
function resetCurrentPart(state) {
|
|
781
|
-
return {
|
|
782
|
-
...state,
|
|
783
|
-
currentField: null,
|
|
784
|
-
currentFilename: void 0,
|
|
785
|
-
currentContentLength: 0,
|
|
786
|
-
currentBufferChunks: [],
|
|
787
|
-
currentStream: null,
|
|
788
|
-
streamController: null,
|
|
789
|
-
currentTempPath: null,
|
|
790
|
-
currentWriteStream: null
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
function addToCollection(collection, key, value) {
|
|
794
|
-
const newCollection = new Map(collection);
|
|
795
|
-
const existing = newCollection.get(key) || [];
|
|
796
|
-
newCollection.set(key, [...existing, value]);
|
|
797
|
-
return newCollection;
|
|
798
|
-
}
|
|
799
|
-
async function finalize(state) {
|
|
800
|
-
if (!state.hasFoundValidBoundary) {
|
|
801
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
802
|
-
throw new ValidationError2("No valid multipart boundary found");
|
|
803
|
-
}
|
|
804
|
-
if (state.hasFoundValidBoundary && !state.hasProcessedAnyPart) {
|
|
805
|
-
const { ValidationError: ValidationError2 } = await import("./validation-error-TXMSFWZL.js");
|
|
806
|
-
throw new ValidationError2("Empty multipart request");
|
|
807
|
-
}
|
|
808
|
-
const fields = {};
|
|
809
|
-
for (const [key, values] of state.fields.entries()) {
|
|
810
|
-
fields[key] = values.length === 1 ? values[0] : values;
|
|
811
|
-
}
|
|
812
|
-
const files = {};
|
|
813
|
-
for (const [key, fileList] of state.files.entries()) {
|
|
814
|
-
files[key] = fileList.length === 1 ? fileList[0] : fileList;
|
|
815
|
-
}
|
|
816
|
-
return { fields, files };
|
|
817
|
-
}
|
|
818
|
-
async function cleanup(state) {
|
|
819
|
-
await Promise.allSettled(state.cleanupTasks.map((task) => task()));
|
|
820
|
-
if (state.streamController) {
|
|
821
|
-
state.streamController.close();
|
|
822
|
-
}
|
|
823
|
-
if (state.currentWriteStream) {
|
|
824
|
-
await closeStream(state.currentWriteStream);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
async function writeToStream(stream, chunk) {
|
|
828
|
-
return new Promise((resolve3, reject) => {
|
|
829
|
-
stream.write(chunk, (error) => {
|
|
830
|
-
if (error) reject(error);
|
|
831
|
-
else resolve3();
|
|
832
|
-
});
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
async function closeStream(stream) {
|
|
836
|
-
return new Promise((resolve3) => {
|
|
837
|
-
stream.end(() => resolve3());
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
async function parseMultipartRequest(request, options = {}) {
|
|
841
|
-
const contentType = request.headers["content-type"] || "";
|
|
842
|
-
const boundary = extractBoundary(contentType);
|
|
843
|
-
if (!boundary) {
|
|
844
|
-
const { UnsupportedMediaTypeError: UnsupportedMediaTypeError2 } = await import("./unsupported-media-type-error-VVHRDTUH.js");
|
|
845
|
-
throw new UnsupportedMediaTypeError2("Missing boundary in multipart content-type", {
|
|
846
|
-
receivedContentType: contentType,
|
|
847
|
-
expectedFormat: "multipart/form-data; boundary=..."
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
let state = createParserState(boundary, options);
|
|
851
|
-
if (state.currentFilename !== void 0) {
|
|
852
|
-
state = await initializeFileProcessing(state);
|
|
853
|
-
}
|
|
854
|
-
try {
|
|
855
|
-
for await (const chunk of request) {
|
|
856
|
-
state = await processChunk(state, chunk);
|
|
857
|
-
}
|
|
858
|
-
return finalize(state);
|
|
859
|
-
} finally {
|
|
860
|
-
await cleanup(state);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// src/context/create.ts
|
|
865
|
-
var CONTENT_TYPE_HEADER = "Content-Type";
|
|
866
|
-
var DEFAULT_BODY_LIMITS = {
|
|
867
|
-
json: 512 * 1024,
|
|
868
|
-
// 512KB - Most APIs should be much smaller
|
|
869
|
-
form: 1024 * 1024,
|
|
870
|
-
// 1MB - Reasonable for form submissions
|
|
871
|
-
text: 5 * 1024 * 1024,
|
|
872
|
-
// 5MB - Documents, logs, code files
|
|
873
|
-
multipart: {
|
|
874
|
-
maxFileSize: 50 * 1024 * 1024,
|
|
875
|
-
// 50MB per file
|
|
876
|
-
maxTotalSize: 100 * 1024 * 1024,
|
|
877
|
-
// 100MB total request
|
|
878
|
-
maxFiles: 10,
|
|
879
|
-
maxFieldSize: 1024 * 1024
|
|
880
|
-
// 1MB for form fields
|
|
881
|
-
},
|
|
882
|
-
raw: 10 * 1024 * 1024
|
|
883
|
-
// 10MB for unknown content types
|
|
884
|
-
};
|
|
885
|
-
function parseRequestUrl(req) {
|
|
886
|
-
const originalUrl = req.url || "/";
|
|
887
|
-
const host = req.headers.host || "localhost";
|
|
888
|
-
const protocol = req.socket && req.socket.encrypted ? "https" : "http";
|
|
889
|
-
const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
|
|
890
|
-
try {
|
|
891
|
-
const url = new URL(fullUrl);
|
|
892
|
-
const path6 = url.pathname;
|
|
893
|
-
const query = {};
|
|
894
|
-
url.searchParams.forEach((value, key) => {
|
|
895
|
-
if (query[key] !== void 0) {
|
|
896
|
-
if (Array.isArray(query[key])) {
|
|
897
|
-
query[key].push(value);
|
|
898
|
-
} else {
|
|
899
|
-
query[key] = [query[key], value];
|
|
900
|
-
}
|
|
901
|
-
} else {
|
|
902
|
-
query[key] = value;
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
return { path: path6, url, query };
|
|
906
|
-
} catch (error) {
|
|
907
|
-
console.warn(`Invalid URL: ${fullUrl}`, error);
|
|
908
|
-
throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
function isHttp2Request(req) {
|
|
912
|
-
return "stream" in req || "httpVersionMajor" in req && req.httpVersionMajor === 2;
|
|
913
|
-
}
|
|
914
|
-
function getProtocol(req) {
|
|
915
|
-
const encrypted = req.socket && req.socket.encrypted;
|
|
916
|
-
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
917
|
-
if (forwardedProto) {
|
|
918
|
-
if (Array.isArray(forwardedProto)) {
|
|
919
|
-
return forwardedProto[0]?.split(",")[0]?.trim() || "http";
|
|
920
|
-
} else {
|
|
921
|
-
return forwardedProto.split(",")[0]?.trim() || "http";
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return encrypted ? "https" : "http";
|
|
925
|
-
}
|
|
926
|
-
async function createContext(req, res, options = {}) {
|
|
927
|
-
const { path: path6, url, query } = parseRequestUrl(req);
|
|
928
|
-
const method = req.method || "GET";
|
|
929
|
-
const isHttp2 = isHttp2Request(req);
|
|
930
|
-
const protocol = getProtocol(req);
|
|
931
|
-
const params = {};
|
|
932
|
-
const state = { ...options.initialState || {} };
|
|
933
|
-
const responseState = { sent: false };
|
|
934
|
-
const ctx = {
|
|
935
|
-
request: createRequestObject(req, {
|
|
936
|
-
path: path6,
|
|
937
|
-
url,
|
|
938
|
-
query,
|
|
939
|
-
params,
|
|
940
|
-
method,
|
|
941
|
-
isHttp2,
|
|
942
|
-
protocol
|
|
943
|
-
}),
|
|
944
|
-
response: {},
|
|
945
|
-
state
|
|
946
|
-
};
|
|
947
|
-
ctx.response = createResponseObject(res, responseState, ctx);
|
|
948
|
-
if (options.parseBody) {
|
|
949
|
-
await parseBodyIfNeeded(req, ctx, options);
|
|
950
|
-
}
|
|
951
|
-
return ctx;
|
|
952
|
-
}
|
|
953
|
-
function createRequestObject(req, info) {
|
|
954
|
-
return {
|
|
955
|
-
raw: req,
|
|
956
|
-
...info,
|
|
957
|
-
header: createRequestHeaderGetter(req),
|
|
958
|
-
headers: createRequestHeadersGetter(req),
|
|
959
|
-
body: void 0
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
function createRequestHeaderGetter(req) {
|
|
963
|
-
return (name) => {
|
|
964
|
-
const value = req.headers[name.toLowerCase()];
|
|
965
|
-
if (Array.isArray(value)) {
|
|
966
|
-
return value.join(", ");
|
|
967
|
-
}
|
|
968
|
-
return value || void 0;
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
function createRequestHeadersGetter(req) {
|
|
972
|
-
const headerGetter = createRequestHeaderGetter(req);
|
|
973
|
-
return (names) => {
|
|
974
|
-
if (names && Array.isArray(names) && names.length > 0) {
|
|
975
|
-
return names.reduce((acc, name) => {
|
|
976
|
-
acc[name] = headerGetter(name);
|
|
977
|
-
return acc;
|
|
978
|
-
}, {});
|
|
979
|
-
} else {
|
|
980
|
-
return Object.entries(req.headers).reduce(
|
|
981
|
-
(acc, [key, value]) => {
|
|
982
|
-
acc[key] = Array.isArray(value) ? value.join(", ") : value || void 0;
|
|
983
|
-
return acc;
|
|
984
|
-
},
|
|
985
|
-
{}
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
function createResponseObject(res, responseState, ctx) {
|
|
991
|
-
return {
|
|
992
|
-
raw: res,
|
|
993
|
-
get sent() {
|
|
994
|
-
return responseState.sent;
|
|
995
|
-
},
|
|
996
|
-
status: createStatusSetter(res, responseState, ctx),
|
|
997
|
-
header: createHeaderSetter(res, responseState, ctx),
|
|
998
|
-
headers: createHeadersSetter(res, responseState, ctx),
|
|
999
|
-
type: createContentTypeSetter(res, responseState, ctx),
|
|
1000
|
-
json: createJsonResponder(res, responseState),
|
|
1001
|
-
text: createTextResponder(res, responseState),
|
|
1002
|
-
html: createHtmlResponder(res, responseState),
|
|
1003
|
-
redirect: createRedirectResponder(res, responseState),
|
|
1004
|
-
stream: createStreamResponder(res, responseState)
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
function createStatusSetter(res, responseState, ctx) {
|
|
1008
|
-
return function statusSetter(code) {
|
|
1009
|
-
if (responseState.sent) {
|
|
1010
|
-
throw new ResponseSentError();
|
|
1011
|
-
}
|
|
1012
|
-
res.statusCode = code;
|
|
1013
|
-
return ctx.response;
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
function createHeaderSetter(res, responseState, ctx) {
|
|
1017
|
-
return function headerSetter(name, value) {
|
|
1018
|
-
if (responseState.sent) {
|
|
1019
|
-
throw new ResponseSentHeaderError();
|
|
1020
|
-
}
|
|
1021
|
-
res.setHeader(name, value);
|
|
1022
|
-
return ctx.response;
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
function createHeadersSetter(res, responseState, ctx) {
|
|
1026
|
-
return function headersSetter(headers) {
|
|
1027
|
-
if (responseState.sent) {
|
|
1028
|
-
throw new ResponseSentHeaderError();
|
|
1029
|
-
}
|
|
1030
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
1031
|
-
res.setHeader(name, value);
|
|
1032
|
-
}
|
|
1033
|
-
return ctx.response;
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
function createContentTypeSetter(res, responseState, ctx) {
|
|
1037
|
-
return function typeSetter(type) {
|
|
1038
|
-
if (responseState.sent) {
|
|
1039
|
-
throw new ResponseSentContentError();
|
|
1040
|
-
}
|
|
1041
|
-
res.setHeader(CONTENT_TYPE_HEADER, type);
|
|
1042
|
-
return ctx.response;
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
function createJsonResponder(res, responseState) {
|
|
1046
|
-
return function jsonResponder(body, status) {
|
|
1047
|
-
if (responseState.sent) {
|
|
1048
|
-
throw new ResponseSentError();
|
|
1049
|
-
}
|
|
1050
|
-
if (status !== void 0) {
|
|
1051
|
-
res.statusCode = status;
|
|
1052
|
-
}
|
|
1053
|
-
res.setHeader(CONTENT_TYPE_HEADER, "application/json");
|
|
1054
|
-
res.end(JSON.stringify(body));
|
|
1055
|
-
responseState.sent = true;
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
function createTextResponder(res, responseState) {
|
|
1059
|
-
return function textResponder(body, status) {
|
|
1060
|
-
if (responseState.sent) {
|
|
1061
|
-
throw new ResponseSentError();
|
|
1062
|
-
}
|
|
1063
|
-
if (status !== void 0) {
|
|
1064
|
-
res.statusCode = status;
|
|
1065
|
-
}
|
|
1066
|
-
res.setHeader(CONTENT_TYPE_HEADER, "text/plain");
|
|
1067
|
-
res.end(body);
|
|
1068
|
-
responseState.sent = true;
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
function createHtmlResponder(res, responseState) {
|
|
1072
|
-
return function htmlResponder(body, status) {
|
|
1073
|
-
if (responseState.sent) {
|
|
1074
|
-
throw new ResponseSentError();
|
|
1075
|
-
}
|
|
1076
|
-
if (status !== void 0) {
|
|
1077
|
-
res.statusCode = status;
|
|
1078
|
-
}
|
|
1079
|
-
res.setHeader(CONTENT_TYPE_HEADER, "text/html");
|
|
1080
|
-
res.end(body);
|
|
1081
|
-
responseState.sent = true;
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
function createRedirectResponder(res, responseState) {
|
|
1085
|
-
return function redirectResponder(url, status = 302) {
|
|
1086
|
-
if (responseState.sent) {
|
|
1087
|
-
throw new ResponseSentError();
|
|
1088
|
-
}
|
|
1089
|
-
res.statusCode = status;
|
|
1090
|
-
res.setHeader("Location", url);
|
|
1091
|
-
res.end();
|
|
1092
|
-
responseState.sent = true;
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
function createStreamResponder(res, responseState) {
|
|
1096
|
-
return function streamResponder(readable, options = {}) {
|
|
1097
|
-
if (responseState.sent) {
|
|
1098
|
-
throw new ResponseSentError();
|
|
1099
|
-
}
|
|
1100
|
-
if (options.status !== void 0) {
|
|
1101
|
-
res.statusCode = options.status;
|
|
1102
|
-
}
|
|
1103
|
-
if (options.contentType) {
|
|
1104
|
-
res.setHeader(CONTENT_TYPE_HEADER, options.contentType);
|
|
1105
|
-
}
|
|
1106
|
-
if (options.headers) {
|
|
1107
|
-
for (const [name, value] of Object.entries(options.headers)) {
|
|
1108
|
-
res.setHeader(name, value);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
readable.pipe(res);
|
|
1112
|
-
readable.on("end", () => {
|
|
1113
|
-
responseState.sent = true;
|
|
1114
|
-
});
|
|
1115
|
-
readable.on("error", (err) => {
|
|
1116
|
-
console.error("Stream error:", err);
|
|
1117
|
-
if (!responseState.sent) {
|
|
1118
|
-
res.statusCode = 500;
|
|
1119
|
-
res.end("Stream error");
|
|
1120
|
-
responseState.sent = true;
|
|
1121
|
-
}
|
|
1122
|
-
});
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
async function parseBodyIfNeeded(req, ctx, options = {}) {
|
|
1126
|
-
if (shouldSkipParsing(req.method)) {
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
const contentType = req.headers["content-type"] || "";
|
|
1130
|
-
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
|
1131
|
-
if (contentLength === 0) {
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
const limits = {
|
|
1135
|
-
json: options.bodyLimits?.json ?? DEFAULT_BODY_LIMITS.json,
|
|
1136
|
-
form: options.bodyLimits?.form ?? DEFAULT_BODY_LIMITS.form,
|
|
1137
|
-
text: options.bodyLimits?.text ?? DEFAULT_BODY_LIMITS.text,
|
|
1138
|
-
raw: options.bodyLimits?.raw ?? DEFAULT_BODY_LIMITS.raw,
|
|
1139
|
-
multipart: {
|
|
1140
|
-
maxFileSize: options.bodyLimits?.multipart?.maxFileSize ?? DEFAULT_BODY_LIMITS.multipart.maxFileSize,
|
|
1141
|
-
maxFiles: options.bodyLimits?.multipart?.maxFiles ?? DEFAULT_BODY_LIMITS.multipart.maxFiles,
|
|
1142
|
-
maxFieldSize: options.bodyLimits?.multipart?.maxFieldSize ?? DEFAULT_BODY_LIMITS.multipart.maxFieldSize,
|
|
1143
|
-
maxTotalSize: options.bodyLimits?.multipart?.maxTotalSize ?? DEFAULT_BODY_LIMITS.multipart.maxTotalSize
|
|
1144
|
-
}
|
|
1145
|
-
};
|
|
1146
|
-
try {
|
|
1147
|
-
if (contentType.includes("application/json")) {
|
|
1148
|
-
if (contentLength > limits.json) {
|
|
1149
|
-
throw new Error(`JSON body too large: ${contentLength} > ${limits.json} bytes`);
|
|
1150
|
-
}
|
|
1151
|
-
await parseJsonBody(req, ctx);
|
|
1152
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1153
|
-
if (contentLength > limits.form) {
|
|
1154
|
-
throw new Error(`Form body too large: ${contentLength} > ${limits.form} bytes`);
|
|
1155
|
-
}
|
|
1156
|
-
await parseFormUrlEncodedBody(req, ctx);
|
|
1157
|
-
} else if (contentType.includes("text/")) {
|
|
1158
|
-
if (contentLength > limits.text) {
|
|
1159
|
-
throw new Error(`Text body too large: ${contentLength} > ${limits.text} bytes`);
|
|
1160
|
-
}
|
|
1161
|
-
await parseTextBody(req, ctx);
|
|
1162
|
-
} else if (isMultipartContent(contentType)) {
|
|
1163
|
-
await parseMultipartBody(req, ctx, limits.multipart);
|
|
1164
|
-
} else {
|
|
1165
|
-
if (contentLength > limits.raw) {
|
|
1166
|
-
throw new Error(`Request body too large: ${contentLength} > ${limits.raw} bytes`);
|
|
1167
|
-
}
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
} catch (error) {
|
|
1171
|
-
const errorType = contentType.includes("multipart") ? "multipart_parse_error" : "body_read_error";
|
|
1172
|
-
setBodyError(ctx, errorType, "Error reading request body", error);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
function shouldSkipParsing(method) {
|
|
1176
|
-
const skipMethods = ["GET", "HEAD", "OPTIONS"];
|
|
1177
|
-
return skipMethods.includes(method || "GET");
|
|
1178
|
-
}
|
|
1179
|
-
async function parseJsonBody(req, ctx) {
|
|
1180
|
-
const body = await readRequestBody(req);
|
|
1181
|
-
if (!body) {
|
|
1182
|
-
console.warn("Empty body, skipping JSON parsing");
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
if (body.trim() === "null") {
|
|
1186
|
-
console.warn('Body is the string "null"');
|
|
1187
|
-
ctx.request.body = null;
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
try {
|
|
1191
|
-
const json = JSON.parse(body);
|
|
1192
|
-
ctx.request.body = json;
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
ctx.request.body = null;
|
|
1195
|
-
setBodyError(ctx, "json_parse_error", "Invalid JSON in request body", error);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
async function parseFormUrlEncodedBody(req, ctx) {
|
|
1199
|
-
const body = await readRequestBody(req);
|
|
1200
|
-
if (!body) return;
|
|
1201
|
-
try {
|
|
1202
|
-
ctx.request.body = parseUrlEncodedData(body);
|
|
1203
|
-
} catch (error) {
|
|
1204
|
-
ctx.request.body = null;
|
|
1205
|
-
setBodyError(ctx, "form_parse_error", "Invalid form data in request body", error);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
function parseUrlEncodedData(body) {
|
|
1209
|
-
const params = new URLSearchParams(body);
|
|
1210
|
-
const formData = {};
|
|
1211
|
-
params.forEach((value, key) => {
|
|
1212
|
-
if (formData[key] !== void 0) {
|
|
1213
|
-
if (Array.isArray(formData[key])) {
|
|
1214
|
-
formData[key].push(value);
|
|
1215
|
-
} else {
|
|
1216
|
-
formData[key] = [formData[key], value];
|
|
1217
|
-
}
|
|
1218
|
-
} else {
|
|
1219
|
-
formData[key] = value;
|
|
1220
|
-
}
|
|
1221
|
-
});
|
|
1222
|
-
return formData;
|
|
1223
|
-
}
|
|
1224
|
-
async function parseTextBody(req, ctx) {
|
|
1225
|
-
const body = await readRequestBody(req);
|
|
1226
|
-
if (body) {
|
|
1227
|
-
ctx.request.body = body;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
async function parseMultipartBody(req, ctx, multipartLimits) {
|
|
1231
|
-
try {
|
|
1232
|
-
const limits = multipartLimits || DEFAULT_BODY_LIMITS.multipart;
|
|
1233
|
-
const multipartData = await parseMultipartRequest(req, {
|
|
1234
|
-
strategy: "stream",
|
|
1235
|
-
maxFileSize: limits.maxFileSize,
|
|
1236
|
-
maxFiles: limits.maxFiles,
|
|
1237
|
-
maxFieldSize: limits.maxFieldSize
|
|
1238
|
-
// Could add total size validation here
|
|
1239
|
-
});
|
|
1240
|
-
ctx.request.multipart = multipartData;
|
|
1241
|
-
ctx.request.files = multipartData.files;
|
|
1242
|
-
ctx.request.body = multipartData.fields;
|
|
1243
|
-
} catch (error) {
|
|
1244
|
-
ctx.request.body = null;
|
|
1245
|
-
setBodyError(ctx, "multipart_parse_error", "Failed to parse multipart data", error);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
function setBodyError(ctx, type, message, error) {
|
|
1249
|
-
const bodyError = { type, message, error };
|
|
1250
|
-
ctx.state._bodyError = bodyError;
|
|
1251
|
-
}
|
|
1252
|
-
async function readRequestBody(req) {
|
|
1253
|
-
return new Promise((resolve3, reject) => {
|
|
1254
|
-
const chunks = [];
|
|
1255
|
-
req.on("data", (chunk) => {
|
|
1256
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1257
|
-
});
|
|
1258
|
-
req.on("end", () => {
|
|
1259
|
-
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
1260
|
-
});
|
|
1261
|
-
req.on("error", (err) => {
|
|
1262
|
-
reject(err);
|
|
1263
|
-
});
|
|
1264
|
-
});
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// src/errors/not-found-error.ts
|
|
1268
|
-
var NotFoundError = class extends BlaizeError {
|
|
1269
|
-
/**
|
|
1270
|
-
* Creates a new NotFoundError instance
|
|
1271
|
-
*
|
|
1272
|
-
* @param title - Human-readable error message
|
|
1273
|
-
* @param details - Optional context about the missing resource
|
|
1274
|
-
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
1275
|
-
*/
|
|
1276
|
-
constructor(title, details = void 0, correlationId = void 0) {
|
|
1277
|
-
super(
|
|
1278
|
-
"NOT_FOUND" /* NOT_FOUND */,
|
|
1279
|
-
title,
|
|
1280
|
-
404,
|
|
1281
|
-
// HTTP 404 Not Found
|
|
1282
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
1283
|
-
details
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
|
|
1288
|
-
// src/errors/boundary.ts
|
|
1289
|
-
function isHandledError(error) {
|
|
1290
|
-
return error instanceof BlaizeError;
|
|
1291
|
-
}
|
|
1292
|
-
function formatErrorResponse(error) {
|
|
1293
|
-
if (isHandledError(error)) {
|
|
1294
|
-
return {
|
|
1295
|
-
type: error.type,
|
|
1296
|
-
title: error.title,
|
|
1297
|
-
status: error.status,
|
|
1298
|
-
correlationId: error.correlationId,
|
|
1299
|
-
timestamp: error.timestamp.toISOString(),
|
|
1300
|
-
details: error.details
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
const correlationId = generateCorrelationId();
|
|
1304
|
-
let originalMessage;
|
|
1305
|
-
if (error instanceof Error) {
|
|
1306
|
-
originalMessage = error.message;
|
|
1307
|
-
} else if (error === null || error === void 0) {
|
|
1308
|
-
originalMessage = "Unknown error occurred";
|
|
1309
|
-
} else {
|
|
1310
|
-
originalMessage = String(error);
|
|
1311
|
-
}
|
|
1312
|
-
const wrappedError = new InternalServerError(
|
|
1313
|
-
"Internal Server Error",
|
|
1314
|
-
{ originalMessage },
|
|
1315
|
-
correlationId
|
|
1316
|
-
);
|
|
1317
|
-
return {
|
|
1318
|
-
type: wrappedError.type,
|
|
1319
|
-
title: wrappedError.title,
|
|
1320
|
-
status: wrappedError.status,
|
|
1321
|
-
correlationId: wrappedError.correlationId,
|
|
1322
|
-
timestamp: wrappedError.timestamp.toISOString(),
|
|
1323
|
-
details: wrappedError.details
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
function extractOrGenerateCorrelationId(headerGetter) {
|
|
1327
|
-
return headerGetter("x-correlation-id") ?? generateCorrelationId();
|
|
1328
|
-
}
|
|
1329
|
-
function setErrorResponseHeaders(headerSetter, correlationId) {
|
|
1330
|
-
headerSetter("x-correlation-id", correlationId);
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// src/middleware/error-boundary.ts
|
|
1334
|
-
function createErrorBoundary(options = {}) {
|
|
1335
|
-
const { debug = false } = options;
|
|
1336
|
-
const middlewareFn = async (ctx, next) => {
|
|
1337
|
-
try {
|
|
1338
|
-
await next();
|
|
1339
|
-
} catch (error) {
|
|
1340
|
-
if (ctx.response.sent) {
|
|
1341
|
-
if (debug) {
|
|
1342
|
-
console.error("Error occurred after response was sent:", error);
|
|
1343
|
-
}
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
if (debug) {
|
|
1347
|
-
console.error("Error boundary caught error:", error);
|
|
1348
|
-
}
|
|
1349
|
-
const correlationId = extractOrGenerateCorrelationId(ctx.request.header);
|
|
1350
|
-
const errorResponse = formatErrorResponse(error);
|
|
1351
|
-
errorResponse.correlationId = correlationId;
|
|
1352
|
-
setErrorResponseHeaders(ctx.response.header, correlationId);
|
|
1353
|
-
ctx.response.status(errorResponse.status).json(errorResponse);
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
return {
|
|
1357
|
-
name: "ErrorBoundary",
|
|
1358
|
-
execute: middlewareFn,
|
|
1359
|
-
debug
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// src/server/request-handler.ts
|
|
1364
|
-
function createRequestHandler(serverInstance) {
|
|
1365
|
-
return async (req, res) => {
|
|
1366
|
-
try {
|
|
1367
|
-
const context = await createContext(req, res, {
|
|
1368
|
-
parseBody: true
|
|
1369
|
-
// Enable automatic body parsing
|
|
1370
|
-
});
|
|
1371
|
-
const errorBoundary = createErrorBoundary();
|
|
1372
|
-
const allMiddleware = [errorBoundary, ...serverInstance.middleware];
|
|
1373
|
-
const handler = compose(allMiddleware);
|
|
1374
|
-
await runWithContext(context, async () => {
|
|
1375
|
-
await handler(context, async () => {
|
|
1376
|
-
if (!context.response.sent) {
|
|
1377
|
-
await serverInstance.router.handleRequest(context);
|
|
1378
|
-
if (!context.response.sent) {
|
|
1379
|
-
throw new NotFoundError(
|
|
1380
|
-
`Route not found: ${context.request.method} ${context.request.path}`
|
|
1381
|
-
);
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
});
|
|
1385
|
-
});
|
|
1386
|
-
} catch (error) {
|
|
1387
|
-
console.error("Error creating context:", error);
|
|
1388
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1389
|
-
res.end(
|
|
1390
|
-
JSON.stringify({
|
|
1391
|
-
error: "Internal Server Error",
|
|
1392
|
-
message: "Failed to process request"
|
|
1393
|
-
})
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// src/server/start.ts
|
|
1400
|
-
async function prepareCertificates(http2Options) {
|
|
1401
|
-
if (!http2Options.enabled) {
|
|
1402
|
-
return {};
|
|
1403
|
-
}
|
|
1404
|
-
const { keyFile, certFile } = http2Options;
|
|
1405
|
-
const isDevMode = process.env.NODE_ENV === "development";
|
|
1406
|
-
const certificatesMissing = !keyFile || !certFile;
|
|
1407
|
-
if (certificatesMissing && isDevMode) {
|
|
1408
|
-
const devCerts = await generateDevCertificates();
|
|
1409
|
-
return devCerts;
|
|
1410
|
-
}
|
|
1411
|
-
if (certificatesMissing) {
|
|
1412
|
-
throw new Error(
|
|
1413
|
-
"HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically."
|
|
1414
|
-
);
|
|
1415
|
-
}
|
|
1416
|
-
return { keyFile, certFile };
|
|
1417
|
-
}
|
|
1418
|
-
function createServerInstance(isHttp2, certOptions) {
|
|
1419
|
-
if (!isHttp2) {
|
|
1420
|
-
return http.createServer();
|
|
1421
|
-
}
|
|
1422
|
-
const http2ServerOptions = {
|
|
1423
|
-
allowHTTP1: true
|
|
1424
|
-
// Allow fallback to HTTP/1.1
|
|
1425
|
-
};
|
|
1426
|
-
try {
|
|
1427
|
-
if (certOptions.keyFile) {
|
|
1428
|
-
http2ServerOptions.key = fs2.readFileSync(certOptions.keyFile);
|
|
1429
|
-
}
|
|
1430
|
-
if (certOptions.certFile) {
|
|
1431
|
-
http2ServerOptions.cert = fs2.readFileSync(certOptions.certFile);
|
|
1432
|
-
}
|
|
1433
|
-
} catch (err) {
|
|
1434
|
-
throw new Error(
|
|
1435
|
-
`Failed to read certificate files: ${err instanceof Error ? err.message : String(err)}`
|
|
1436
|
-
);
|
|
1437
|
-
}
|
|
1438
|
-
return http2.createSecureServer(http2ServerOptions);
|
|
1439
|
-
}
|
|
1440
|
-
function listenOnPort(server, port, host, isHttp2) {
|
|
1441
|
-
return new Promise((resolve3, reject) => {
|
|
1442
|
-
server.listen(port, host, () => {
|
|
1443
|
-
const protocol = isHttp2 ? "https" : "http";
|
|
1444
|
-
const url = `${protocol}://${host}:${port}`;
|
|
1445
|
-
console.log(`
|
|
10
|
+
import{a as H}from"./chunk-HSLLYUVO.js";import{a as U}from"./chunk-TL4GIFTB.js";import{a as Et}from"./chunk-EE2VJ6JY.js";import{a as Ct}from"./chunk-VLVWNGUO.js";import{a as A,b as B,c as mo,d as S,e as go,f as oe,g as x}from"./chunk-DTDGIBMA.js";function Me(e,t,r){if(!e||e.skip&&e.skip(t))return Promise.resolve(r());try{let o=e.execute(t,r);return o instanceof Promise?o:Promise.resolve(o)}catch(o){return Promise.reject(o)}}function D(e){return e.length===0?async(t,r)=>{await Promise.resolve(r())}:async function(t,r){let o=new Set,n=async i=>{if(i>=e.length)return Promise.resolve(r());let s=e[i];return Me(s,t,()=>{if(o.has(i))throw new Error("next() called multiple times");return o.add(i),n(i+1)})};return n(0)}}function ne(e){if(typeof e=="function")return{name:"anonymous",execute:e,debug:!1};let{name:t="anonymous",handler:r,skip:o,debug:n=!1}=e,i={name:t,execute:r,debug:n};return o!==void 0?{...i,skip:o}:i}function ie(e,t,r,o={}){if(!e||typeof e!="string")throw new Error("Plugin name must be a non-empty string");if(!t||typeof t!="string")throw new Error("Plugin version must be a non-empty string");if(typeof r!="function")throw new Error("Plugin setup must be a function");return function(i){let s={...o,...i},a={name:e,version:t,register:async u=>{let c=await r(u,s);c&&typeof c=="object"&&Object.assign(a,c)}};return a}}import{fileURLToPath as Ft}from"node:url";var V={};function ze(e){V={...V,...e}}function Be(){if(!V.routesDir)throw new Error("Routes directory not configured. Make sure server is properly initialized.");return V.routesDir}import*as $e from"node:path";function W(e,t){e.startsWith("file://")&&(e=e.replace("file://","")),t.startsWith("file://")&&(t=t.replace("file://",""));let r=e.replace(/\\/g,"/"),o=t.replace(/\\/g,"/"),n=o.endsWith("/")?o:`${o}/`,i=r;r.startsWith(n)?i=r.substring(n.length):r.startsWith(o)?(i=r.substring(o.length),i.startsWith("/")&&(i=i.substring(1))):i=$e.relative(o,r).replace(/\\/g,"/"),i=i.replace(/\.[^.]+$/,"");let s=i.split("/").filter(Boolean),a=[],u=s.map(w=>{if(w.startsWith("[")&&w.endsWith("]")){let d=w.slice(1,-1);return a.push(d),`:${d}`}return w}),c=u.length>0?`/${u.join("/")}`:"/";return c.endsWith("/index")&&(c=c.slice(0,-6)||"/"),{filePath:e,routePath:c,params:a}}function Mt(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(n,i)=>i;let r=new Error().stack[3];if(!r||typeof r.getFileName!="function")throw new Error("Unable to determine caller file frame");let o=r.getFileName();if(!o)throw new Error("Unable to determine caller file name");return o.startsWith("file://")?Ft(o):o}finally{Error.prepareStackTrace=e}}function $(){let e=Mt(),t=Be(),r=W(e,t);return console.log(`\u{1F50E} Parsed route path: ${r.routePath} from file: ${e}`),r.routePath}var Oe=e=>{O("GET",e);let t=$();return{GET:e,path:t}},ke=e=>{O("POST",e);let t=$();return{POST:e,path:t}},De=e=>{O("PUT",e);let t=$();return{PUT:e,path:t}},Ne=e=>{O("DELETE",e);let t=$();return{DELETE:e,path:t}},Ae=e=>{O("PATCH",e);let t=$();return{PATCH:e,path:t}},He=e=>{O("HEAD",e);let t=$();return{HEAD:e,path:t}},Ue=e=>{O("OPTIONS",e);let t=$();return{OPTIONS:e,path:t}};function O(e,t){if(!t.handler||typeof t.handler!="function")throw new Error(`Handler for method ${e} must be a function`);if(t.middleware&&!Array.isArray(t.middleware))throw new Error(`Middleware for method ${e} must be an array`);switch(t.schema&&zt(e,t.schema),e){case"GET":case"HEAD":case"DELETE":t.schema?.body&&console.warn(`Warning: ${e} requests typically don't have request bodies`);break}}function zt(e,t){let{params:r,query:o,body:n,response:i}=t;if(r&&(!r._def||typeof r.parse!="function"))throw new Error(`Params schema for ${e} must be a valid Zod schema`);if(o&&(!o._def||typeof o.parse!="function"))throw new Error(`Query schema for ${e} must be a valid Zod schema`);if(n&&(!n._def||typeof n.parse!="function"))throw new Error(`Body schema for ${e} must be a valid Zod schema`);if(i&&(!i._def||typeof i.parse!="function"))throw new Error(`Response schema for ${e} must be a valid Zod schema`)}import{AsyncLocalStorage as Xr}from"node:async_hooks";import Kr from"node:events";import*as ae from"node:fs";import*as it from"node:http";import*as st from"node:http2";import*as E from"node:fs";import*as G from"node:path";import*as qe from"selfsigned";async function Ie(){let e=G.join(process.cwd(),".blaizejs","certs"),t=G.join(e,"dev.key"),r=G.join(e,"dev.cert");if(E.existsSync(t)&&E.existsSync(r))return{keyFile:t,certFile:r};E.existsSync(e)||E.mkdirSync(e,{recursive:!0});let i=qe.generate([{name:"commonName",value:"localhost"}],{days:365,algorithm:"sha256",keySize:2048,extensions:[{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"extKeyUsage",serverAuth:!0,clientAuth:!0},{name:"subjectAltName",altNames:[{type:2,value:"localhost"},{type:7,ip:"127.0.0.1"}]}]});return E.writeFileSync(t,Buffer.from(i.private,"utf-8")),E.writeFileSync(r,Buffer.from(i.cert,"utf-8")),console.log(`
|
|
11
|
+
\u{1F512} Generated self-signed certificates for development at ${e}
|
|
12
|
+
`),{keyFile:t,certFile:r}}var T=class extends Error{constructor(t="\u274C Response has already been sent"){super(t),this.name="ResponseSentError"}},q=class extends T{constructor(t="Cannot set header after response has been sent"){super(t)}},Z=class extends T{constructor(t="Cannot set content type after response has been sent"){super(t)}},J=class extends T{constructor(t="Invalide URL"){super(t)}};import{AsyncLocalStorage as Bt}from"node:async_hooks";var $t=new Bt;function Le(e,t){return $t.run(e,t)}import*as Ge from"node:crypto";import{createWriteStream as At}from"node:fs";import{tmpdir as Ht}from"node:os";import{join as Ut}from"node:path";import{Readable as We}from"node:stream";var Ot=/boundary=([^;]+)/i,kt=/Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;[\s\r\n]*filename="([^"]*)")?/i,Dt=/Content-Type:\s*([^\r\n]+)/i,Nt=/multipart\/form-data/i;function je(e){let t=e.match(Ot);if(!t||!t[1])return null;let r=t[1].trim();return r.startsWith('"')&&r.endsWith('"')&&(r=r.slice(1,-1)),r||null}function _e(e){let t=e.match(kt);return!t||!t[1]?null:{name:t[1],filename:t[2]!==void 0?t[2]:void 0}}function Qe(e){let t=e.match(Dt);return t&&t[1]?.trim()?t[1].trim():"application/octet-stream"}function Ve(e){return Nt.test(e)}var qt={maxFileSize:10*1024*1024,maxFiles:10,maxFieldSize:1*1024*1024,allowedMimeTypes:[],allowedExtensions:[],strategy:"stream",tempDir:Ht(),computeHash:!1};function It(e,t={}){return{boundary:Buffer.from(`--${e}`),options:{...qt,...t},fields:new Map,files:new Map,buffer:Buffer.alloc(0),stage:"boundary",currentHeaders:"",currentField:null,currentFilename:void 0,currentMimetype:"application/octet-stream",currentContentLength:0,fileCount:0,fieldCount:0,currentBufferChunks:[],currentStream:null,currentTempPath:null,currentWriteStream:null,streamController:null,cleanupTasks:[],hasFoundValidBoundary:!1,hasProcessedAnyPart:!1,isFinished:!1}}async function Lt(e,t){let r=Buffer.concat([e.buffer,t]),o={...e,buffer:r};for(;o.buffer.length>0&&!o.isFinished;){let n=await jt(o);if(n===o)break;o=n}return o}async function jt(e){switch(e.stage){case"boundary":return _t(e);case"headers":return Qt(e);case"content":return Vt(e);default:{let{InternalServerError:t}=await import("./internal-server-error-GWBNT3OO.js");throw new t("Invalid parser stage",{operation:e.stage})}}}function _t(e){let t=e.buffer.indexOf(e.boundary);if(t===-1)return e;let r=!0,o=e.buffer.subarray(t+e.boundary.length);return o.length>=2&&o.subarray(0,2).equals(Buffer.from("--"))?{...e,buffer:o,hasFoundValidBoundary:r,isFinished:!0,stage:"boundary"}:(o.length>=2&&o.subarray(0,2).equals(Buffer.from(`\r
|
|
13
|
+
`))&&(o=o.subarray(2)),{...e,buffer:o,hasFoundValidBoundary:r,stage:"headers",currentHeaders:""})}async function Qt(e){let t=e.buffer.indexOf(`\r
|
|
14
|
+
\r
|
|
15
|
+
`);if(t===-1)return e;let r=e.buffer.subarray(0,t).toString("utf8"),o=e.buffer.subarray(t+4),n=_e(r);if(!n){let{ValidationError:a}=await import("./validation-error-6JDCGV2S.js");throw new a("Missing or invalid Content-Disposition header")}let i=Qe(r),s=n.filename!==void 0;if(s&&e.fileCount>=e.options.maxFiles){let{PayloadTooLargeError:a}=await import("./payload-too-large-error-EBM5BNWG.js");throw new a("Too many files in upload",{fileCount:e.fileCount+1,maxFiles:e.options.maxFiles,filename:n.filename})}if(s&&e.options.allowedMimeTypes.length>0&&!e.options.allowedMimeTypes.includes(i)){let{UnsupportedMediaTypeError:a}=await import("./unsupported-media-type-error-YQ7GCZ32.js");throw new a("File type not allowed",{receivedMimeType:i,allowedMimeTypes:e.options.allowedMimeTypes,filename:n.filename})}return{...e,buffer:o,stage:"content",currentHeaders:r,currentField:n.name,currentFilename:n.filename,currentMimetype:i,currentContentLength:0,fileCount:s?e.fileCount+1:e.fileCount,fieldCount:s?e.fieldCount:e.fieldCount+1,currentBufferChunks:[]}}async function Vt(e){let t=e.buffer.indexOf(e.boundary),r,o=!1,n=e.buffer;if(t===-1){let s=Math.max(0,e.buffer.length-e.boundary.length);if(s===0)return e;r=e.buffer.subarray(0,s),n=e.buffer.subarray(s)}else{let s=Math.max(0,t-2);r=e.buffer.subarray(0,s),n=e.buffer.subarray(t),o=!0}let i={...e,buffer:n};return r.length>0&&(i=await Wt(i,r)),o&&(i=await Jt(i),i={...i,stage:"boundary",hasProcessedAnyPart:!0}),i}async function Wt(e,t){let r=e.currentContentLength+t.length,o=e.currentFilename!==void 0?e.options.maxFileSize:e.options.maxFieldSize;if(r>o){let n=e.currentFilename!==void 0,{PayloadTooLargeError:i}=await import("./payload-too-large-error-EBM5BNWG.js"),s=e.currentField?{contentType:n?"file":"field",currentSize:r,maxSize:o,field:e.currentField,filename:e.currentFilename}:{contentType:n?"file":"field",currentSize:r,maxSize:o,filename:e.currentFilename};throw new i(`${n?"File":"Field"} size exceeds limit`,s)}return e.currentFilename!==void 0?Gt(e,t,r):{...e,currentContentLength:r,currentBufferChunks:[...e.currentBufferChunks,t]}}async function Gt(e,t,r){switch(e.options.strategy){case"memory":return{...e,currentContentLength:r,currentBufferChunks:[...e.currentBufferChunks,t]};case"stream":return e.streamController&&e.streamController.enqueue(t),{...e,currentContentLength:r};case"temp":return e.currentWriteStream&&await tr(e.currentWriteStream,t),{...e,currentContentLength:r};default:{let{ValidationError:o}=await import("./validation-error-6JDCGV2S.js");throw new o("Invalid parsing strategy")}}}async function Zt(e){if(e.currentFilename===void 0)return e;switch(e.options.strategy){case"memory":return{...e,currentBufferChunks:[]};case"stream":{let t=null,r=new ReadableStream({start:o=>{t=o}});return{...e,currentStream:r,streamController:t}}case"temp":{let t=Ut(e.options.tempDir,`upload-${Ge.randomUUID()}`),r=At(t),o=async()=>{try{let{unlink:n}=await import("node:fs/promises");await n(t)}catch(n){console.warn(`Failed to cleanup temp file: ${t}`,n)}};return{...e,currentTempPath:t,currentWriteStream:r,cleanupTasks:[...e.cleanupTasks,o]}}default:{let{ValidationError:t}=await import("./validation-error-6JDCGV2S.js");throw new t("Invalid file processing strategy")}}}async function Jt(e){return e.currentField?e.currentFilename!==void 0?Yt(e):Xt(e):I(e)}async function Yt(e){if(!e.currentField||e.currentFilename===void 0)return I(e);let t,r,o;switch(e.options.strategy){case"memory":r=Buffer.concat(e.currentBufferChunks),t=We.from(r);break;case"stream":e.streamController&&e.streamController.close(),t=e.currentStream;break;case"temp":e.currentWriteStream&&await Je(e.currentWriteStream),o=e.currentTempPath,t=We.from(Buffer.alloc(0));break;default:{let{ValidationError:s}=await import("./validation-error-6JDCGV2S.js");throw new s("Invalid file finalization strategy")}}let n={filename:e.currentFilename,fieldname:e.currentField,mimetype:e.currentMimetype,size:e.currentContentLength,stream:t,buffer:r,tempPath:o},i=Ze(e.files,e.currentField,n);return{...I(e),files:i}}function Xt(e){if(!e.currentField)return I(e);let t=Buffer.concat(e.currentBufferChunks).toString("utf8"),r=Ze(e.fields,e.currentField,t);return{...I(e),fields:r}}function I(e){return{...e,currentField:null,currentFilename:void 0,currentContentLength:0,currentBufferChunks:[],currentStream:null,streamController:null,currentTempPath:null,currentWriteStream:null}}function Ze(e,t,r){let o=new Map(e),n=o.get(t)||[];return o.set(t,[...n,r]),o}async function Kt(e){if(!e.hasFoundValidBoundary){let{ValidationError:o}=await import("./validation-error-6JDCGV2S.js");throw new o("No valid multipart boundary found")}if(e.hasFoundValidBoundary&&!e.hasProcessedAnyPart){let{ValidationError:o}=await import("./validation-error-6JDCGV2S.js");throw new o("Empty multipart request")}let t={};for(let[o,n]of e.fields.entries())t[o]=n.length===1?n[0]:n;let r={};for(let[o,n]of e.files.entries())r[o]=n.length===1?n[0]:n;return{fields:t,files:r}}async function er(e){await Promise.allSettled(e.cleanupTasks.map(t=>t())),e.streamController&&e.streamController.close(),e.currentWriteStream&&await Je(e.currentWriteStream)}async function tr(e,t){return new Promise((r,o)=>{e.write(t,n=>{n?o(n):r()})})}async function Je(e){return new Promise(t=>{e.end(()=>t())})}async function Ye(e,t={}){let r=e.headers["content-type"]||"",o=je(r);if(!o){let{UnsupportedMediaTypeError:i}=await import("./unsupported-media-type-error-YQ7GCZ32.js");throw new i("Missing boundary in multipart content-type",{receivedContentType:r,expectedFormat:"multipart/form-data; boundary=..."})}let n=It(o,t);n.currentFilename!==void 0&&(n=await Zt(n));try{for await(let i of e)n=await Lt(n,i);return Kt(n)}finally{await er(n)}}var L="Content-Type",C={json:512*1024,form:1024*1024,text:5*1024*1024,multipart:{maxFileSize:50*1024*1024,maxTotalSize:100*1024*1024,maxFiles:10,maxFieldSize:1024*1024},raw:10*1024*1024};function rr(e){let t=e.url||"/",r=e.headers.host||"localhost",n=`${e.socket&&e.socket.encrypted?"https":"http"}://${r}${t.startsWith("/")?"":"/"}${t}`;try{let i=new URL(n),s=i.pathname,a={};return i.searchParams.forEach((u,c)=>{a[c]!==void 0?Array.isArray(a[c])?a[c].push(u):a[c]=[a[c],u]:a[c]=u}),{path:s,url:i,query:a}}catch(i){throw console.warn(`Invalid URL: ${n}`,i),new J(`Invalid URL: ${n}`)}}function or(e){return"stream"in e||"httpVersionMajor"in e&&e.httpVersionMajor===2}function nr(e){let t=e.socket&&e.socket.encrypted,r=e.headers["x-forwarded-proto"];return r?Array.isArray(r)?r[0]?.split(",")[0]?.trim()||"http":r.split(",")[0]?.trim()||"http":t?"https":"http"}async function Xe(e,t,r={}){let{path:o,url:n,query:i}=rr(e),s=e.method||"GET",a=or(e),u=nr(e),c={},w={...r.initialState||{}},d={sent:!1},g={request:ir(e,{path:o,url:n,query:i,params:c,method:s,isHttp2:a,protocol:u}),response:{},state:w};return g.response=ar(t,d,g),r.parseBody&&await hr(e,g,r),g}function ir(e,t){return{raw:e,...t,header:Ke(e),headers:sr(e),body:void 0}}function Ke(e){return t=>{let r=e.headers[t.toLowerCase()];return Array.isArray(r)?r.join(", "):r||void 0}}function sr(e){let t=Ke(e);return r=>r&&Array.isArray(r)&&r.length>0?r.reduce((o,n)=>(o[n]=t(n),o),{}):Object.entries(e.headers).reduce((o,[n,i])=>(o[n]=Array.isArray(i)?i.join(", "):i||void 0,o),{})}function ar(e,t,r){return{raw:e,get sent(){return t.sent},status:ur(e,t,r),header:lr(e,t,r),headers:cr(e,t,r),type:dr(e,t,r),json:pr(e,t),text:fr(e,t),html:mr(e,t),redirect:gr(e,t),stream:yr(e,t)}}function ur(e,t,r){return function(n){if(t.sent)throw new T;return e.statusCode=n,r.response}}function lr(e,t,r){return function(n,i){if(t.sent)throw new q;return e.setHeader(n,i),r.response}}function cr(e,t,r){return function(n){if(t.sent)throw new q;for(let[i,s]of Object.entries(n))e.setHeader(i,s);return r.response}}function dr(e,t,r){return function(n){if(t.sent)throw new Z;return e.setHeader(L,n),r.response}}function pr(e,t){return function(o,n){if(t.sent)throw new T;n!==void 0&&(e.statusCode=n),e.setHeader(L,"application/json"),e.end(JSON.stringify(o)),t.sent=!0}}function fr(e,t){return function(o,n){if(t.sent)throw new T;n!==void 0&&(e.statusCode=n),e.setHeader(L,"text/plain"),e.end(o),t.sent=!0}}function mr(e,t){return function(o,n){if(t.sent)throw new T;n!==void 0&&(e.statusCode=n),e.setHeader(L,"text/html"),e.end(o),t.sent=!0}}function gr(e,t){return function(o,n=302){if(t.sent)throw new T;e.statusCode=n,e.setHeader("Location",o),e.end(),t.sent=!0}}function yr(e,t){return function(o,n={}){if(t.sent)throw new T;if(n.status!==void 0&&(e.statusCode=n.status),n.contentType&&e.setHeader(L,n.contentType),n.headers)for(let[i,s]of Object.entries(n.headers))e.setHeader(i,s);o.pipe(e),o.on("end",()=>{t.sent=!0}),o.on("error",i=>{console.error("Stream error:",i),t.sent||(e.statusCode=500,e.end("Stream error"),t.sent=!0)})}}async function hr(e,t,r={}){if(wr(e.method))return;let o=e.headers["content-type"]||"",n=parseInt(e.headers["content-length"]||"0",10);if(n===0)return;let i={json:r.bodyLimits?.json??C.json,form:r.bodyLimits?.form??C.form,text:r.bodyLimits?.text??C.text,raw:r.bodyLimits?.raw??C.raw,multipart:{maxFileSize:r.bodyLimits?.multipart?.maxFileSize??C.multipart.maxFileSize,maxFiles:r.bodyLimits?.multipart?.maxFiles??C.multipart.maxFiles,maxFieldSize:r.bodyLimits?.multipart?.maxFieldSize??C.multipart.maxFieldSize,maxTotalSize:r.bodyLimits?.multipart?.maxTotalSize??C.multipart.maxTotalSize}};try{if(o.includes("application/json")){if(n>i.json)throw new Error(`JSON body too large: ${n} > ${i.json} bytes`);await Rr(e,t)}else if(o.includes("application/x-www-form-urlencoded")){if(n>i.form)throw new Error(`Form body too large: ${n} > ${i.form} bytes`);await Sr(e,t)}else if(o.includes("text/")){if(n>i.text)throw new Error(`Text body too large: ${n} > ${i.text} bytes`);await br(e,t)}else if(Ve(o))await Tr(e,t,i.multipart);else{if(n>i.raw)throw new Error(`Request body too large: ${n} > ${i.raw} bytes`);return}}catch(s){let a=o.includes("multipart")?"multipart_parse_error":"body_read_error";Y(t,a,"Error reading request body",s)}}function wr(e){return["GET","HEAD","OPTIONS"].includes(e||"GET")}async function Rr(e,t){let r=await se(e);if(!r){console.warn("Empty body, skipping JSON parsing");return}if(r.trim()==="null"){console.warn('Body is the string "null"'),t.request.body=null;return}try{let o=JSON.parse(r);t.request.body=o}catch(o){t.request.body=null,Y(t,"json_parse_error","Invalid JSON in request body",o)}}async function Sr(e,t){let r=await se(e);if(r)try{t.request.body=xr(r)}catch(o){t.request.body=null,Y(t,"form_parse_error","Invalid form data in request body",o)}}function xr(e){let t=new URLSearchParams(e),r={};return t.forEach((o,n)=>{r[n]!==void 0?Array.isArray(r[n])?r[n].push(o):r[n]=[r[n],o]:r[n]=o}),r}async function br(e,t){let r=await se(e);r&&(t.request.body=r)}async function Tr(e,t,r){try{let o=r||C.multipart,n=await Ye(e,{strategy:"stream",maxFileSize:o.maxFileSize,maxFiles:o.maxFiles,maxFieldSize:o.maxFieldSize});t.request.multipart=n,t.request.files=n.files,t.request.body=n.fields}catch(o){t.request.body=null,Y(t,"multipart_parse_error","Failed to parse multipart data",o)}}function Y(e,t,r,o){let n={type:t,message:r,error:o};e.state._bodyError=n}async function se(e){return new Promise((t,r)=>{let o=[];e.on("data",n=>{o.push(Buffer.isBuffer(n)?n:Buffer.from(n))}),e.on("end",()=>{t(Buffer.concat(o).toString("utf8"))}),e.on("error",n=>{r(n)})})}var k=class extends S{constructor(t,r=void 0,o=void 0){super("NOT_FOUND",t,404,o??x(),r)}};function Pr(e){return e instanceof S}function et(e){if(Pr(e))return{type:e.type,title:e.title,status:e.status,correlationId:e.correlationId,timestamp:e.timestamp.toISOString(),details:e.details};let t=oe(),r;e instanceof Error?r=e.message:e==null?r="Unknown error occurred":r=String(e);let o=new H("Internal Server Error",{originalMessage:r},t);return{type:o.type,title:o.title,status:o.status,correlationId:o.correlationId,timestamp:o.timestamp.toISOString(),details:o.details}}function tt(e){return e("x-correlation-id")??oe()}function rt(e,t){e("x-correlation-id",t)}function ot(e={}){let{debug:t=!1}=e;return{name:"ErrorBoundary",execute:async(o,n)=>{try{await n()}catch(i){if(o.response.sent){t&&console.error("Error occurred after response was sent:",i);return}t&&console.error("Error boundary caught error:",i);let s=tt(o.request.header),a=et(i);a.correlationId=s,rt(o.response.header,s),o.response.status(a.status).json(a)}},debug:t}}function nt(e){return async(t,r)=>{try{let o=await Xe(t,r,{parseBody:!0}),i=[ot(),...e.middleware],s=D(i);await Le(o,async()=>{await s(o,async()=>{if(!o.response.sent&&(await e.router.handleRequest(o),!o.response.sent))throw new k(`Route not found: ${o.request.method} ${o.request.path}`)})})}catch(o){console.error("Error creating context:",o),r.writeHead(500,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:"Internal Server Error",message:"Failed to process request"}))}}}async function vr(e){if(!e.enabled)return{};let{keyFile:t,certFile:r}=e,o=process.env.NODE_ENV==="development",n=!t||!r;if(n&&o)return await Ie();if(n)throw new Error("HTTP/2 requires SSL certificates. Provide keyFile and certFile in http2 options. In development, set NODE_ENV=development to generate them automatically.");return{keyFile:t,certFile:r}}function Er(e,t){if(!e)return it.createServer();let r={allowHTTP1:!0};try{t.keyFile&&(r.key=ae.readFileSync(t.keyFile)),t.certFile&&(r.cert=ae.readFileSync(t.certFile))}catch(o){throw new Error(`Failed to read certificate files: ${o instanceof Error?o.message:String(o)}`)}return st.createSecureServer(r)}function Cr(e,t,r,o){return new Promise((n,i)=>{e.listen(t,r,()=>{let a=`${o?"https":"http"}://${r}:${t}`;console.log(`
|
|
1446
16
|
\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
|
|
1447
17
|
|
|
1448
18
|
\u26A1 BlaizeJS DEVELOPMENT SERVER HOT AND READY \u26A1
|
|
1449
19
|
|
|
1450
|
-
\u{1F680} Server: ${
|
|
20
|
+
\u{1F680} Server: ${a}
|
|
1451
21
|
\u{1F525} Hot Reload: Enabled
|
|
1452
22
|
\u{1F6E0}\uFE0F Mode: Development
|
|
1453
23
|
|
|
1454
24
|
Time to build something amazing! \u{1F680}
|
|
1455
25
|
|
|
1456
26
|
\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
|
|
1457
|
-
`);
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
console.error("Server error:", err);
|
|
1462
|
-
reject(err);
|
|
1463
|
-
});
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
async function initializePlugins(serverInstance) {
|
|
1467
|
-
for (const plugin of serverInstance.plugins) {
|
|
1468
|
-
if (typeof plugin.initialize === "function") {
|
|
1469
|
-
await plugin.initialize(serverInstance);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
async function startServer(serverInstance, serverOptions) {
|
|
1474
|
-
if (serverInstance.server) {
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
try {
|
|
1478
|
-
const port = serverOptions.port;
|
|
1479
|
-
const host = serverOptions.host;
|
|
1480
|
-
await initializePlugins(serverInstance);
|
|
1481
|
-
const http2Options = serverOptions.http2 || { enabled: true };
|
|
1482
|
-
const isHttp2 = !!http2Options.enabled;
|
|
1483
|
-
const certOptions = await prepareCertificates(http2Options);
|
|
1484
|
-
if (serverOptions.http2 && certOptions.keyFile && certOptions.certFile) {
|
|
1485
|
-
serverOptions.http2.keyFile = certOptions.keyFile;
|
|
1486
|
-
serverOptions.http2.certFile = certOptions.certFile;
|
|
1487
|
-
}
|
|
1488
|
-
const server = createServerInstance(isHttp2, certOptions);
|
|
1489
|
-
serverInstance.server = server;
|
|
1490
|
-
serverInstance.port = port;
|
|
1491
|
-
serverInstance.host = host;
|
|
1492
|
-
const requestHandler = createRequestHandler(serverInstance);
|
|
1493
|
-
server.on("request", requestHandler);
|
|
1494
|
-
await listenOnPort(server, port, host, isHttp2);
|
|
1495
|
-
} catch (error) {
|
|
1496
|
-
console.error("Failed to start server:", error);
|
|
1497
|
-
throw error;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// src/server/stop.ts
|
|
1502
|
-
var isShuttingDown = false;
|
|
1503
|
-
async function stopServer(serverInstance, options = {}) {
|
|
1504
|
-
const server = serverInstance.server;
|
|
1505
|
-
const events = serverInstance.events;
|
|
1506
|
-
if (isShuttingDown) {
|
|
1507
|
-
console.log("\u26A0\uFE0F Shutdown already in progress, ignoring duplicate shutdown request");
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
if (!server) {
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
isShuttingDown = true;
|
|
1514
|
-
const timeout = options.timeout || 5e3;
|
|
1515
|
-
try {
|
|
1516
|
-
if (options.onStopping) {
|
|
1517
|
-
await options.onStopping();
|
|
1518
|
-
}
|
|
1519
|
-
events.emit("stopping");
|
|
1520
|
-
if (serverInstance.router && typeof serverInstance.router.close === "function") {
|
|
1521
|
-
console.log("\u{1F50C} Closing router watchers...");
|
|
1522
|
-
try {
|
|
1523
|
-
await Promise.race([
|
|
1524
|
-
serverInstance.router.close(),
|
|
1525
|
-
new Promise(
|
|
1526
|
-
(_, reject) => setTimeout(() => reject(new Error("Router close timeout")), 2e3)
|
|
1527
|
-
)
|
|
1528
|
-
]);
|
|
1529
|
-
console.log("\u2705 Router watchers closed");
|
|
1530
|
-
} catch (error) {
|
|
1531
|
-
console.error("\u274C Error closing router watchers:", error);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
try {
|
|
1535
|
-
await Promise.race([
|
|
1536
|
-
serverInstance.pluginManager.onServerStop(serverInstance, server),
|
|
1537
|
-
new Promise(
|
|
1538
|
-
(_, reject) => setTimeout(() => reject(new Error("Plugin stop timeout")), 2e3)
|
|
1539
|
-
)
|
|
1540
|
-
]);
|
|
1541
|
-
} catch (error) {
|
|
1542
|
-
console.error("\u274C Plugin stop timeout:", error);
|
|
1543
|
-
}
|
|
1544
|
-
const closePromise = new Promise((resolve3, reject) => {
|
|
1545
|
-
server.close((err) => {
|
|
1546
|
-
if (err) return reject(err);
|
|
1547
|
-
resolve3();
|
|
1548
|
-
});
|
|
1549
|
-
});
|
|
1550
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
1551
|
-
setTimeout(() => {
|
|
1552
|
-
reject(new Error("Server shutdown timeout"));
|
|
1553
|
-
}, timeout);
|
|
1554
|
-
});
|
|
1555
|
-
await Promise.race([closePromise, timeoutPromise]);
|
|
1556
|
-
try {
|
|
1557
|
-
await Promise.race([
|
|
1558
|
-
serverInstance.pluginManager.terminatePlugins(serverInstance),
|
|
1559
|
-
new Promise(
|
|
1560
|
-
(_, reject) => setTimeout(() => reject(new Error("Plugin terminate timeout")), 1e3)
|
|
1561
|
-
)
|
|
1562
|
-
]);
|
|
1563
|
-
} catch (error) {
|
|
1564
|
-
console.error("\u274C Plugin terminate timeout:", error);
|
|
1565
|
-
}
|
|
1566
|
-
if (options.onStopped) {
|
|
1567
|
-
await options.onStopped();
|
|
1568
|
-
}
|
|
1569
|
-
events.emit("stopped");
|
|
1570
|
-
serverInstance.server = null;
|
|
1571
|
-
console.log("\u2705 Graceful shutdown completed");
|
|
1572
|
-
isShuttingDown = false;
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
isShuttingDown = false;
|
|
1575
|
-
console.error("\u26A0\uFE0F Shutdown error (forcing exit):", error);
|
|
1576
|
-
if (server && typeof server.close === "function") {
|
|
1577
|
-
server.close();
|
|
1578
|
-
}
|
|
1579
|
-
if (process.env.NODE_ENV === "development") {
|
|
1580
|
-
console.log("\u{1F504} Forcing exit for development restart...");
|
|
1581
|
-
process.exit(0);
|
|
1582
|
-
}
|
|
1583
|
-
events.emit("error", error);
|
|
1584
|
-
throw error;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
function registerSignalHandlers(stopFn) {
|
|
1588
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
1589
|
-
if (isDevelopment) {
|
|
1590
|
-
const sigintHandler = () => {
|
|
1591
|
-
console.log("\u{1F4E4} SIGINT received, forcing exit for development restart...");
|
|
1592
|
-
process.exit(0);
|
|
1593
|
-
};
|
|
1594
|
-
const sigtermHandler = () => {
|
|
1595
|
-
console.log("\u{1F4E4} SIGTERM received, forcing exit for development restart...");
|
|
1596
|
-
process.exit(0);
|
|
1597
|
-
};
|
|
1598
|
-
process.on("SIGINT", sigintHandler);
|
|
1599
|
-
process.on("SIGTERM", sigtermHandler);
|
|
1600
|
-
return {
|
|
1601
|
-
unregister: () => {
|
|
1602
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
1603
|
-
process.removeListener("SIGTERM", sigtermHandler);
|
|
1604
|
-
}
|
|
1605
|
-
};
|
|
1606
|
-
} else {
|
|
1607
|
-
const sigintHandler = () => {
|
|
1608
|
-
console.log("\u{1F4E4} SIGINT received, starting graceful shutdown...");
|
|
1609
|
-
stopFn().catch(console.error);
|
|
1610
|
-
};
|
|
1611
|
-
const sigtermHandler = () => {
|
|
1612
|
-
console.log("\u{1F4E4} SIGTERM received, starting graceful shutdown...");
|
|
1613
|
-
stopFn().catch(console.error);
|
|
1614
|
-
};
|
|
1615
|
-
process.on("SIGINT", sigintHandler);
|
|
1616
|
-
process.on("SIGTERM", sigtermHandler);
|
|
1617
|
-
return {
|
|
1618
|
-
unregister: () => {
|
|
1619
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
1620
|
-
process.removeListener("SIGTERM", sigtermHandler);
|
|
1621
|
-
}
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// src/server/validation.ts
|
|
1627
|
-
import { z } from "zod";
|
|
1628
|
-
var middlewareSchema = z.custom(
|
|
1629
|
-
(data) => data !== null && typeof data === "object" && "execute" in data && typeof data.execute === "function",
|
|
1630
|
-
{
|
|
1631
|
-
message: "Expected middleware to have an execute function"
|
|
1632
|
-
}
|
|
1633
|
-
);
|
|
1634
|
-
var pluginSchema = z.custom(
|
|
1635
|
-
(data) => data !== null && typeof data === "object" && "register" in data && typeof data.register === "function",
|
|
1636
|
-
{
|
|
1637
|
-
message: "Expected a valid plugin object with a register method"
|
|
1638
|
-
}
|
|
1639
|
-
);
|
|
1640
|
-
var http2Schema = z.object({
|
|
1641
|
-
enabled: z.boolean().optional().default(true),
|
|
1642
|
-
keyFile: z.string().optional(),
|
|
1643
|
-
certFile: z.string().optional()
|
|
1644
|
-
}).refine(
|
|
1645
|
-
(data) => {
|
|
1646
|
-
if (data.enabled && process.env.NODE_ENV === "production") {
|
|
1647
|
-
return data.keyFile && data.certFile;
|
|
1648
|
-
}
|
|
1649
|
-
return true;
|
|
1650
|
-
},
|
|
1651
|
-
{
|
|
1652
|
-
message: "When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"
|
|
1653
|
-
}
|
|
1654
|
-
);
|
|
1655
|
-
var serverOptionsSchema = z.object({
|
|
1656
|
-
port: z.number().int().positive().optional().default(3e3),
|
|
1657
|
-
host: z.string().optional().default("localhost"),
|
|
1658
|
-
routesDir: z.string().optional().default("./routes"),
|
|
1659
|
-
http2: http2Schema.optional().default({
|
|
1660
|
-
enabled: true
|
|
1661
|
-
}),
|
|
1662
|
-
middleware: z.array(middlewareSchema).optional().default([]),
|
|
1663
|
-
plugins: z.array(pluginSchema).optional().default([])
|
|
1664
|
-
});
|
|
1665
|
-
function validateServerOptions(options) {
|
|
1666
|
-
try {
|
|
1667
|
-
return serverOptionsSchema.parse(options);
|
|
1668
|
-
} catch (error) {
|
|
1669
|
-
if (error instanceof z.ZodError) {
|
|
1670
|
-
const formattedError = error.format();
|
|
1671
|
-
throw new Error(`Invalid server options: ${JSON.stringify(formattedError, null, 2)}`);
|
|
1672
|
-
}
|
|
1673
|
-
throw new Error(`Invalid server options: ${String(error)}`);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// src/plugins/lifecycle.ts
|
|
1678
|
-
function createPluginLifecycleManager(options = {}) {
|
|
1679
|
-
const { continueOnError = true, debug = false, onError } = options;
|
|
1680
|
-
function log(message, ...args) {
|
|
1681
|
-
if (debug) {
|
|
1682
|
-
console.log(`[PluginLifecycle] ${message}`, ...args);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
function handleError(plugin, phase, error) {
|
|
1686
|
-
const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
|
|
1687
|
-
if (onError) {
|
|
1688
|
-
onError(plugin, phase, error);
|
|
1689
|
-
} else {
|
|
1690
|
-
console.error(errorMessage, error);
|
|
1691
|
-
}
|
|
1692
|
-
if (!continueOnError) {
|
|
1693
|
-
throw new Error(errorMessage);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
return {
|
|
1697
|
-
/**
|
|
1698
|
-
* Initialize all plugins
|
|
1699
|
-
*/
|
|
1700
|
-
async initializePlugins(server) {
|
|
1701
|
-
log("Initializing plugins...");
|
|
1702
|
-
for (const plugin of server.plugins) {
|
|
1703
|
-
if (plugin.initialize) {
|
|
1704
|
-
try {
|
|
1705
|
-
log(`Initializing plugin: ${plugin.name}`);
|
|
1706
|
-
await plugin.initialize(server);
|
|
1707
|
-
} catch (error) {
|
|
1708
|
-
handleError(plugin, "initialize", error);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
log(`Initialized ${server.plugins.length} plugins`);
|
|
1713
|
-
},
|
|
1714
|
-
/**
|
|
1715
|
-
* Terminate all plugins in reverse order
|
|
1716
|
-
*/
|
|
1717
|
-
async terminatePlugins(server) {
|
|
1718
|
-
log("Terminating plugins...");
|
|
1719
|
-
const pluginsToTerminate = [...server.plugins].reverse();
|
|
1720
|
-
for (const plugin of pluginsToTerminate) {
|
|
1721
|
-
if (plugin.terminate) {
|
|
1722
|
-
try {
|
|
1723
|
-
log(`Terminating plugin: ${plugin.name}`);
|
|
1724
|
-
await plugin.terminate(server);
|
|
1725
|
-
} catch (error) {
|
|
1726
|
-
handleError(plugin, "terminate", error);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
log(`Terminated ${pluginsToTerminate.length} plugins`);
|
|
1731
|
-
},
|
|
1732
|
-
/**
|
|
1733
|
-
* Notify plugins that the server has started
|
|
1734
|
-
*/
|
|
1735
|
-
async onServerStart(server, httpServer) {
|
|
1736
|
-
log("Notifying plugins of server start...");
|
|
1737
|
-
for (const plugin of server.plugins) {
|
|
1738
|
-
if (plugin.onServerStart) {
|
|
1739
|
-
try {
|
|
1740
|
-
log(`Notifying plugin of server start: ${plugin.name}`);
|
|
1741
|
-
await plugin.onServerStart(httpServer);
|
|
1742
|
-
} catch (error) {
|
|
1743
|
-
handleError(plugin, "onServerStart", error);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
},
|
|
1748
|
-
/**
|
|
1749
|
-
* Notify plugins that the server is stopping
|
|
1750
|
-
*/
|
|
1751
|
-
async onServerStop(server, httpServer) {
|
|
1752
|
-
log("Notifying plugins of server stop...");
|
|
1753
|
-
const pluginsToNotify = [...server.plugins].reverse();
|
|
1754
|
-
for (const plugin of pluginsToNotify) {
|
|
1755
|
-
if (plugin.onServerStop) {
|
|
1756
|
-
try {
|
|
1757
|
-
log(`Notifying plugin of server stop: ${plugin.name}`);
|
|
1758
|
-
await plugin.onServerStop(httpServer);
|
|
1759
|
-
} catch (error) {
|
|
1760
|
-
handleError(plugin, "onServerStop", error);
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
};
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
// src/plugins/errors.ts
|
|
1769
|
-
var PluginValidationError = class extends Error {
|
|
1770
|
-
constructor(pluginName, message) {
|
|
1771
|
-
super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
|
|
1772
|
-
this.pluginName = pluginName;
|
|
1773
|
-
this.name = "PluginValidationError";
|
|
1774
|
-
}
|
|
1775
|
-
};
|
|
1776
|
-
|
|
1777
|
-
// src/plugins/validation.ts
|
|
1778
|
-
var RESERVED_NAMES = /* @__PURE__ */ new Set([
|
|
1779
|
-
"core",
|
|
1780
|
-
"server",
|
|
1781
|
-
"router",
|
|
1782
|
-
"middleware",
|
|
1783
|
-
"context",
|
|
1784
|
-
"blaize",
|
|
1785
|
-
"blaizejs"
|
|
1786
|
-
]);
|
|
1787
|
-
var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
1788
|
-
var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
|
|
1789
|
-
function validatePlugin(plugin, options = {}) {
|
|
1790
|
-
const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
|
|
1791
|
-
if (!plugin || typeof plugin !== "object") {
|
|
1792
|
-
throw new PluginValidationError("", "Plugin must be an object");
|
|
1793
|
-
}
|
|
1794
|
-
const p = plugin;
|
|
1795
|
-
if (!p.name || typeof p.name !== "string") {
|
|
1796
|
-
throw new PluginValidationError("", "Plugin must have a name (string)");
|
|
1797
|
-
}
|
|
1798
|
-
if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
|
|
1799
|
-
throw new PluginValidationError(
|
|
1800
|
-
p.name,
|
|
1801
|
-
"Plugin name must be lowercase letters, numbers, and hyphens only"
|
|
1802
|
-
);
|
|
1803
|
-
}
|
|
1804
|
-
if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
|
|
1805
|
-
throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
|
|
1806
|
-
}
|
|
1807
|
-
if (requireVersion) {
|
|
1808
|
-
if (!p.version || typeof p.version !== "string") {
|
|
1809
|
-
throw new PluginValidationError(p.name, "Plugin must have a version (string)");
|
|
1810
|
-
}
|
|
1811
|
-
if (!VALID_VERSION_PATTERN.test(p.version)) {
|
|
1812
|
-
throw new PluginValidationError(
|
|
1813
|
-
p.name,
|
|
1814
|
-
'Plugin version must follow semantic versioning (e.g., "1.0.0")'
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
if (!p.register || typeof p.register !== "function") {
|
|
1819
|
-
throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
|
|
1820
|
-
}
|
|
1821
|
-
const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
|
|
1822
|
-
for (const method of lifecycleMethods) {
|
|
1823
|
-
if (p[method] && typeof p[method] !== "function") {
|
|
1824
|
-
throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
// src/router/discovery/cache.ts
|
|
1830
|
-
import * as crypto from "node:crypto";
|
|
1831
|
-
import * as fs3 from "node:fs/promises";
|
|
1832
|
-
import { createRequire } from "node:module";
|
|
1833
|
-
import * as path3 from "node:path";
|
|
1834
|
-
|
|
1835
|
-
// src/router/discovery/loader.ts
|
|
1836
|
-
async function dynamicImport(filePath) {
|
|
1837
|
-
const cacheBuster = `?t=${Date.now()}`;
|
|
1838
|
-
const importPath = filePath + cacheBuster;
|
|
1839
|
-
try {
|
|
1840
|
-
const module = await import(importPath);
|
|
1841
|
-
console.log(`\u2705 Successfully imported module`);
|
|
1842
|
-
return module;
|
|
1843
|
-
} catch (error) {
|
|
1844
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1845
|
-
console.log(`\u26A0\uFE0F Error importing with cache buster, trying original path:`, errorMessage);
|
|
1846
|
-
return import(filePath);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
async function loadRouteModule(filePath, basePath) {
|
|
1850
|
-
try {
|
|
1851
|
-
const parsedRoute = parseRoutePath(filePath, basePath);
|
|
1852
|
-
const module = await dynamicImport(filePath);
|
|
1853
|
-
console.log("\u{1F4E6} Module exports:", Object.keys(module));
|
|
1854
|
-
const routes = [];
|
|
1855
|
-
if (module.default && typeof module.default === "object") {
|
|
1856
|
-
const route = {
|
|
1857
|
-
...module.default,
|
|
1858
|
-
path: parsedRoute.routePath
|
|
1859
|
-
};
|
|
1860
|
-
routes.push(route);
|
|
1861
|
-
}
|
|
1862
|
-
Object.entries(module).forEach(([exportName, exportValue]) => {
|
|
1863
|
-
if (exportName === "default" || !exportValue || typeof exportValue !== "object") {
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
const potentialRoute = exportValue;
|
|
1867
|
-
if (isValidRoute(potentialRoute)) {
|
|
1868
|
-
const route = {
|
|
1869
|
-
...potentialRoute,
|
|
1870
|
-
// Use the route's own path if it has one, otherwise derive from file
|
|
1871
|
-
path: parsedRoute.routePath
|
|
1872
|
-
};
|
|
1873
|
-
routes.push(route);
|
|
1874
|
-
}
|
|
1875
|
-
});
|
|
1876
|
-
if (routes.length === 0) {
|
|
1877
|
-
console.warn(`Route file ${filePath} does not export any valid route definitions`);
|
|
1878
|
-
return [];
|
|
1879
|
-
}
|
|
1880
|
-
console.log(`\u2705 Successfully Loaded ${routes.length} route(s)`);
|
|
1881
|
-
return routes;
|
|
1882
|
-
} catch (error) {
|
|
1883
|
-
console.error(`Failed to load route module ${filePath}:`, error);
|
|
1884
|
-
return [];
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
function isValidRoute(obj) {
|
|
1888
|
-
if (!obj || typeof obj !== "object") {
|
|
1889
|
-
return false;
|
|
1890
|
-
}
|
|
1891
|
-
const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
1892
|
-
const hasHttpMethod = httpMethods.some(
|
|
1893
|
-
(method) => obj[method] && typeof obj[method] === "object" && obj[method].handler
|
|
1894
|
-
);
|
|
1895
|
-
return hasHttpMethod;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
// src/router/discovery/cache.ts
|
|
1899
|
-
var fileRouteCache = /* @__PURE__ */ new Map();
|
|
1900
|
-
async function processChangedFile(filePath, routesDir, updateCache = true) {
|
|
1901
|
-
const stat3 = await fs3.stat(filePath);
|
|
1902
|
-
const lastModified = stat3.mtime.getTime();
|
|
1903
|
-
const cachedEntry = fileRouteCache.get(filePath);
|
|
1904
|
-
if (updateCache && cachedEntry && cachedEntry.timestamp === lastModified) {
|
|
1905
|
-
return cachedEntry.routes;
|
|
1906
|
-
}
|
|
1907
|
-
invalidateModuleCache(filePath);
|
|
1908
|
-
const routes = await loadRouteModule(filePath, routesDir);
|
|
1909
|
-
if (updateCache) {
|
|
1910
|
-
const hash = hashRoutes(routes);
|
|
1911
|
-
fileRouteCache.set(filePath, {
|
|
1912
|
-
routes,
|
|
1913
|
-
timestamp: lastModified,
|
|
1914
|
-
hash
|
|
1915
|
-
});
|
|
1916
|
-
}
|
|
1917
|
-
return routes;
|
|
1918
|
-
}
|
|
1919
|
-
function hasRouteContentChanged(filePath, newRoutes) {
|
|
1920
|
-
const cachedEntry = fileRouteCache.get(filePath);
|
|
1921
|
-
if (!cachedEntry) {
|
|
1922
|
-
return true;
|
|
1923
|
-
}
|
|
1924
|
-
const newHash = hashRoutes(newRoutes);
|
|
1925
|
-
return cachedEntry.hash !== newHash;
|
|
1926
|
-
}
|
|
1927
|
-
function clearFileCache(filePath) {
|
|
1928
|
-
if (filePath) {
|
|
1929
|
-
fileRouteCache.delete(filePath);
|
|
1930
|
-
} else {
|
|
1931
|
-
fileRouteCache.clear();
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
function hashRoutes(routes) {
|
|
1935
|
-
const routeData = routes.map((route) => ({
|
|
1936
|
-
path: route.path,
|
|
1937
|
-
methods: Object.keys(route).filter((key) => key !== "path").sort().map((method) => {
|
|
1938
|
-
const methodDef = route[method];
|
|
1939
|
-
const handlerString = methodDef?.handler ? methodDef.handler.toString() : null;
|
|
1940
|
-
return {
|
|
1941
|
-
method,
|
|
1942
|
-
// Include handler function string for change detection
|
|
1943
|
-
handler: handlerString,
|
|
1944
|
-
// Include middleware if present
|
|
1945
|
-
middleware: methodDef?.middleware ? methodDef.middleware.length : 0,
|
|
1946
|
-
// Include schema structure (but not full serialization which can be unstable)
|
|
1947
|
-
hasSchema: !!methodDef?.schema,
|
|
1948
|
-
schemaKeys: methodDef?.schema ? Object.keys(methodDef.schema).sort() : []
|
|
1949
|
-
};
|
|
1950
|
-
})
|
|
1951
|
-
}));
|
|
1952
|
-
const dataString = JSON.stringify(routeData);
|
|
1953
|
-
const hash = crypto.createHash("md5").update(dataString).digest("hex");
|
|
1954
|
-
return hash;
|
|
1955
|
-
}
|
|
1956
|
-
function invalidateModuleCache(filePath) {
|
|
1957
|
-
try {
|
|
1958
|
-
const absolutePath = path3.resolve(filePath);
|
|
1959
|
-
if (typeof __require !== "undefined") {
|
|
1960
|
-
delete __require.cache[absolutePath];
|
|
1961
|
-
try {
|
|
1962
|
-
const resolvedPath = __require.resolve(absolutePath);
|
|
1963
|
-
delete __require.cache[resolvedPath];
|
|
1964
|
-
} catch (resolveError) {
|
|
1965
|
-
const errorMessage = resolveError instanceof Error ? resolveError.message : String(resolveError);
|
|
1966
|
-
console.log(`\u26A0\uFE0F Could not resolve path: ${errorMessage}`);
|
|
1967
|
-
}
|
|
1968
|
-
} else {
|
|
1969
|
-
try {
|
|
1970
|
-
const require2 = createRequire(import.meta.url);
|
|
1971
|
-
delete require2.cache[absolutePath];
|
|
1972
|
-
try {
|
|
1973
|
-
const resolvedPath = require2.resolve(absolutePath);
|
|
1974
|
-
delete require2.cache[resolvedPath];
|
|
1975
|
-
} catch {
|
|
1976
|
-
console.log(`\u26A0\uFE0F Could not resolve ESM path`);
|
|
1977
|
-
}
|
|
1978
|
-
} catch {
|
|
1979
|
-
console.log(`\u26A0\uFE0F createRequire not available in pure ESM`);
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
} catch (error) {
|
|
1983
|
-
console.log(`\u26A0\uFE0F Error during module cache invalidation for ${filePath}:`, error);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
// src/router/discovery/parallel.ts
|
|
1988
|
-
import * as os from "node:os";
|
|
1989
|
-
|
|
1990
|
-
// src/router/discovery/finder.ts
|
|
1991
|
-
import * as fs4 from "node:fs/promises";
|
|
1992
|
-
import * as path4 from "node:path";
|
|
1993
|
-
async function findRouteFiles(routesDir, options = {}) {
|
|
1994
|
-
const absoluteDir = path4.isAbsolute(routesDir) ? routesDir : path4.resolve(process.cwd(), routesDir);
|
|
1995
|
-
console.log("Creating router with routes directory:", absoluteDir);
|
|
1996
|
-
try {
|
|
1997
|
-
const stats = await fs4.stat(absoluteDir);
|
|
1998
|
-
if (!stats.isDirectory()) {
|
|
1999
|
-
throw new Error(`Route directory is not a directory: ${absoluteDir}`);
|
|
2000
|
-
}
|
|
2001
|
-
} catch (error) {
|
|
2002
|
-
if (error.code === "ENOENT") {
|
|
2003
|
-
throw new Error(`Route directory not found: ${absoluteDir}`);
|
|
2004
|
-
}
|
|
2005
|
-
throw error;
|
|
2006
|
-
}
|
|
2007
|
-
const routeFiles = [];
|
|
2008
|
-
const ignore = options.ignore || ["node_modules", ".git"];
|
|
2009
|
-
async function scanDirectory(dir) {
|
|
2010
|
-
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2011
|
-
for (const entry of entries) {
|
|
2012
|
-
const fullPath = path4.join(dir, entry.name);
|
|
2013
|
-
if (entry.isDirectory() && ignore.includes(entry.name)) {
|
|
2014
|
-
continue;
|
|
2015
|
-
}
|
|
2016
|
-
if (entry.isDirectory()) {
|
|
2017
|
-
await scanDirectory(fullPath);
|
|
2018
|
-
} else if (isRouteFile(entry.name)) {
|
|
2019
|
-
routeFiles.push(fullPath);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
await scanDirectory(absoluteDir);
|
|
2024
|
-
return routeFiles;
|
|
2025
|
-
}
|
|
2026
|
-
function isRouteFile(filename) {
|
|
2027
|
-
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
// src/router/discovery/parallel.ts
|
|
2031
|
-
async function processFilesInParallel(filePaths, processor, concurrency = Math.max(1, Math.floor(os.cpus().length / 2))) {
|
|
2032
|
-
const chunks = chunkArray(filePaths, concurrency);
|
|
2033
|
-
const results = [];
|
|
2034
|
-
for (const chunk of chunks) {
|
|
2035
|
-
const chunkResults = await Promise.allSettled(chunk.map((filePath) => processor(filePath)));
|
|
2036
|
-
const successfulResults = chunkResults.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
2037
|
-
results.push(...successfulResults);
|
|
2038
|
-
}
|
|
2039
|
-
return results;
|
|
2040
|
-
}
|
|
2041
|
-
async function loadInitialRoutesParallel(routesDir) {
|
|
2042
|
-
const files = await findRouteFiles(routesDir);
|
|
2043
|
-
const routeArrays = await processFilesInParallel(
|
|
2044
|
-
files,
|
|
2045
|
-
(filePath) => processChangedFile(filePath, routesDir)
|
|
2046
|
-
);
|
|
2047
|
-
return routeArrays.flat();
|
|
2048
|
-
}
|
|
2049
|
-
function chunkArray(array, chunkSize) {
|
|
2050
|
-
const chunks = [];
|
|
2051
|
-
for (let i = 0; i < array.length; i += chunkSize) {
|
|
2052
|
-
chunks.push(array.slice(i, i + chunkSize));
|
|
2053
|
-
}
|
|
2054
|
-
return chunks;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
// src/router/discovery/profiler.ts
|
|
2058
|
-
var profilerState = {
|
|
2059
|
-
fileChanges: 0,
|
|
2060
|
-
totalReloadTime: 0,
|
|
2061
|
-
averageReloadTime: 0,
|
|
2062
|
-
slowReloads: []
|
|
2063
|
-
};
|
|
2064
|
-
function trackReloadPerformance(filePath, startTime) {
|
|
2065
|
-
const duration = Date.now() - startTime;
|
|
2066
|
-
profilerState.fileChanges++;
|
|
2067
|
-
profilerState.totalReloadTime += duration;
|
|
2068
|
-
profilerState.averageReloadTime = profilerState.totalReloadTime / profilerState.fileChanges;
|
|
2069
|
-
if (duration > 100) {
|
|
2070
|
-
profilerState.slowReloads.push({ file: filePath, time: duration });
|
|
2071
|
-
if (profilerState.slowReloads.length > 10) {
|
|
2072
|
-
profilerState.slowReloads.shift();
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
if (process.env.NODE_ENV === "development") {
|
|
2076
|
-
const emoji = duration < 50 ? "\u26A1" : duration < 100 ? "\u{1F504}" : "\u{1F40C}";
|
|
2077
|
-
console.log(`${emoji} Route reload: ${filePath} (${duration}ms)`);
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
function withPerformanceTracking(fn, filePath) {
|
|
2081
|
-
console.log(`Tracking performance for: ${filePath}`);
|
|
2082
|
-
return async (...args) => {
|
|
2083
|
-
const startTime = Date.now();
|
|
2084
|
-
try {
|
|
2085
|
-
const result = await fn(...args);
|
|
2086
|
-
trackReloadPerformance(filePath, startTime);
|
|
2087
|
-
return result;
|
|
2088
|
-
} catch (error) {
|
|
2089
|
-
trackReloadPerformance(filePath, startTime);
|
|
2090
|
-
throw error;
|
|
2091
|
-
}
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
// src/router/discovery/watchers.ts
|
|
2096
|
-
import * as path5 from "node:path";
|
|
2097
|
-
import { watch } from "chokidar";
|
|
2098
|
-
function watchRoutes(routesDir, options = {}) {
|
|
2099
|
-
const debounceMs = options.debounceMs || 16;
|
|
2100
|
-
const debouncedCallbacks = /* @__PURE__ */ new Map();
|
|
2101
|
-
function createDebouncedCallback(fn, filePath) {
|
|
2102
|
-
return (...args) => {
|
|
2103
|
-
const existingTimeout = debouncedCallbacks.get(filePath);
|
|
2104
|
-
if (existingTimeout) {
|
|
2105
|
-
clearTimeout(existingTimeout);
|
|
2106
|
-
}
|
|
2107
|
-
const timeoutId = setTimeout(() => {
|
|
2108
|
-
fn(...args);
|
|
2109
|
-
debouncedCallbacks.delete(filePath);
|
|
2110
|
-
}, debounceMs);
|
|
2111
|
-
debouncedCallbacks.set(filePath, timeoutId);
|
|
2112
|
-
};
|
|
2113
|
-
}
|
|
2114
|
-
const routesByPath = /* @__PURE__ */ new Map();
|
|
2115
|
-
async function loadInitialRoutes() {
|
|
2116
|
-
try {
|
|
2117
|
-
const files = await findRouteFiles(routesDir, {
|
|
2118
|
-
ignore: options.ignore
|
|
2119
|
-
});
|
|
2120
|
-
for (const filePath of files) {
|
|
2121
|
-
await loadAndNotify(filePath);
|
|
2122
|
-
}
|
|
2123
|
-
} catch (error) {
|
|
2124
|
-
handleError(error);
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
async function loadAndNotify(filePath) {
|
|
2128
|
-
try {
|
|
2129
|
-
const existingRoutes = routesByPath.get(filePath);
|
|
2130
|
-
const newRoutes = await processChangedFile(filePath, routesDir, false);
|
|
2131
|
-
if (!newRoutes || newRoutes.length === 0) {
|
|
2132
|
-
return;
|
|
2133
|
-
}
|
|
2134
|
-
if (existingRoutes && !hasRouteContentChanged(filePath, newRoutes)) {
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
await processChangedFile(filePath, routesDir, true);
|
|
2138
|
-
const normalizedPath = path5.normalize(filePath);
|
|
2139
|
-
if (existingRoutes) {
|
|
2140
|
-
routesByPath.set(filePath, newRoutes);
|
|
2141
|
-
if (options.onRouteChanged) {
|
|
2142
|
-
options.onRouteChanged(normalizedPath, newRoutes);
|
|
2143
|
-
}
|
|
2144
|
-
} else {
|
|
2145
|
-
routesByPath.set(filePath, newRoutes);
|
|
2146
|
-
if (options.onRouteAdded) {
|
|
2147
|
-
options.onRouteAdded(normalizedPath, newRoutes);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
} catch (error) {
|
|
2151
|
-
console.log(`\u26A0\uFE0F Error processing file ${filePath}:`, error);
|
|
2152
|
-
handleError(error);
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
function handleRemoved(filePath) {
|
|
2156
|
-
const normalizedPath = path5.normalize(filePath);
|
|
2157
|
-
const routes = routesByPath.get(normalizedPath);
|
|
2158
|
-
if (routes && routes.length > 0 && options.onRouteRemoved) {
|
|
2159
|
-
options.onRouteRemoved(normalizedPath, routes);
|
|
2160
|
-
}
|
|
2161
|
-
routesByPath.delete(normalizedPath);
|
|
2162
|
-
}
|
|
2163
|
-
function handleError(error) {
|
|
2164
|
-
if (options.onError && error instanceof Error) {
|
|
2165
|
-
options.onError(error);
|
|
2166
|
-
} else {
|
|
2167
|
-
console.error("\u26A0\uFE0F Route watcher error:", error);
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
const watcher = watch(routesDir, {
|
|
2171
|
-
// Much faster response times
|
|
2172
|
-
awaitWriteFinish: {
|
|
2173
|
-
stabilityThreshold: 50,
|
|
2174
|
-
// Reduced from 300ms
|
|
2175
|
-
pollInterval: 10
|
|
2176
|
-
// Reduced from 100ms
|
|
2177
|
-
},
|
|
2178
|
-
// Performance optimizations
|
|
2179
|
-
usePolling: false,
|
|
2180
|
-
atomic: true,
|
|
2181
|
-
followSymlinks: false,
|
|
2182
|
-
depth: 10,
|
|
2183
|
-
// More aggressive ignoring
|
|
2184
|
-
ignored: [
|
|
2185
|
-
/(^|[/\\])\../,
|
|
2186
|
-
/node_modules/,
|
|
2187
|
-
/\.git/,
|
|
2188
|
-
/\.DS_Store/,
|
|
2189
|
-
/Thumbs\.db/,
|
|
2190
|
-
/\.(test|spec)\.(ts|js)$/,
|
|
2191
|
-
/\.d\.ts$/,
|
|
2192
|
-
/\.map$/,
|
|
2193
|
-
/~$/,
|
|
2194
|
-
...options.ignore || []
|
|
2195
|
-
]
|
|
2196
|
-
});
|
|
2197
|
-
watcher.on("add", (filePath) => {
|
|
2198
|
-
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
2199
|
-
debouncedLoad(filePath);
|
|
2200
|
-
}).on("change", (filePath) => {
|
|
2201
|
-
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
2202
|
-
debouncedLoad(filePath);
|
|
2203
|
-
}).on("unlink", (filePath) => {
|
|
2204
|
-
const debouncedRemove = createDebouncedCallback(handleRemoved, filePath);
|
|
2205
|
-
debouncedRemove(filePath);
|
|
2206
|
-
}).on("error", handleError);
|
|
2207
|
-
loadInitialRoutes().catch(handleError);
|
|
2208
|
-
return {
|
|
2209
|
-
close: () => {
|
|
2210
|
-
debouncedCallbacks.forEach((timeout) => clearTimeout(timeout));
|
|
2211
|
-
debouncedCallbacks.clear();
|
|
2212
|
-
return watcher.close();
|
|
2213
|
-
},
|
|
2214
|
-
getRoutes: () => {
|
|
2215
|
-
const allRoutes = [];
|
|
2216
|
-
for (const routes of routesByPath.values()) {
|
|
2217
|
-
allRoutes.push(...routes);
|
|
2218
|
-
}
|
|
2219
|
-
return allRoutes;
|
|
2220
|
-
},
|
|
2221
|
-
getRoutesByFile: () => new Map(routesByPath)
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
// src/router/validation/schema.ts
|
|
2226
|
-
import { z as z6 } from "zod";
|
|
2227
|
-
|
|
2228
|
-
// src/router/validation/body.ts
|
|
2229
|
-
import { z as z2 } from "zod";
|
|
2230
|
-
function validateBody(body, schema) {
|
|
2231
|
-
if (schema instanceof z2.ZodObject) {
|
|
2232
|
-
return schema.strict().parse(body);
|
|
2233
|
-
}
|
|
2234
|
-
return schema.parse(body);
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
// src/router/validation/params.ts
|
|
2238
|
-
import { z as z3 } from "zod";
|
|
2239
|
-
function validateParams(params, schema) {
|
|
2240
|
-
if (schema instanceof z3.ZodObject) {
|
|
2241
|
-
return schema.strict().parse(params);
|
|
2242
|
-
}
|
|
2243
|
-
return schema.parse(params);
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
// src/router/validation/query.ts
|
|
2247
|
-
import { z as z4 } from "zod";
|
|
2248
|
-
function validateQuery(query, schema) {
|
|
2249
|
-
if (schema instanceof z4.ZodObject) {
|
|
2250
|
-
return schema.strict().parse(query);
|
|
2251
|
-
}
|
|
2252
|
-
return schema.parse(query);
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
// src/router/validation/response.ts
|
|
2256
|
-
import { z as z5 } from "zod";
|
|
2257
|
-
function validateResponse(response, schema) {
|
|
2258
|
-
if (schema instanceof z5.ZodObject) {
|
|
2259
|
-
return schema.strict().parse(response);
|
|
2260
|
-
}
|
|
2261
|
-
return schema.parse(response);
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
// src/router/validation/schema.ts
|
|
2265
|
-
function createRequestValidator(schema, debug = false) {
|
|
2266
|
-
const middlewareFn = async (ctx, next) => {
|
|
2267
|
-
if (schema.params && ctx.request.params) {
|
|
2268
|
-
try {
|
|
2269
|
-
ctx.request.params = validateParams(ctx.request.params, schema.params);
|
|
2270
|
-
} catch (error) {
|
|
2271
|
-
const fieldErrors = extractZodFieldErrors(error);
|
|
2272
|
-
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2273
|
-
throw new ValidationError("Request validation failed", {
|
|
2274
|
-
fields: fieldErrors,
|
|
2275
|
-
errorCount,
|
|
2276
|
-
section: "params"
|
|
2277
|
-
});
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
if (schema.query && ctx.request.query) {
|
|
2281
|
-
try {
|
|
2282
|
-
ctx.request.query = validateQuery(ctx.request.query, schema.query);
|
|
2283
|
-
} catch (error) {
|
|
2284
|
-
const fieldErrors = extractZodFieldErrors(error);
|
|
2285
|
-
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2286
|
-
throw new ValidationError("Request validation failed", {
|
|
2287
|
-
fields: fieldErrors,
|
|
2288
|
-
errorCount,
|
|
2289
|
-
section: "query"
|
|
2290
|
-
});
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
if (schema.body) {
|
|
2294
|
-
try {
|
|
2295
|
-
ctx.request.body = validateBody(ctx.request.body, schema.body);
|
|
2296
|
-
} catch (error) {
|
|
2297
|
-
const fieldErrors = extractZodFieldErrors(error);
|
|
2298
|
-
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2299
|
-
throw new ValidationError("Request validation failed", {
|
|
2300
|
-
fields: fieldErrors,
|
|
2301
|
-
errorCount,
|
|
2302
|
-
section: "body"
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
await next();
|
|
2307
|
-
};
|
|
2308
|
-
return {
|
|
2309
|
-
name: "RequestValidator",
|
|
2310
|
-
execute: middlewareFn,
|
|
2311
|
-
debug
|
|
2312
|
-
};
|
|
2313
|
-
}
|
|
2314
|
-
function createResponseValidator(responseSchema, debug = false) {
|
|
2315
|
-
const middlewareFn = async (ctx, next) => {
|
|
2316
|
-
const originalJson = ctx.response.json;
|
|
2317
|
-
ctx.response.json = (body, status) => {
|
|
2318
|
-
try {
|
|
2319
|
-
const validatedBody = validateResponse(body, responseSchema);
|
|
2320
|
-
ctx.response.json = originalJson;
|
|
2321
|
-
return originalJson.call(ctx.response, validatedBody, status);
|
|
2322
|
-
} catch (error) {
|
|
2323
|
-
ctx.response.json = originalJson;
|
|
2324
|
-
throw new InternalServerError("Response validation failed", {
|
|
2325
|
-
responseSchema: responseSchema.description || "Unknown schema",
|
|
2326
|
-
validationError: extractZodFieldErrors(error),
|
|
2327
|
-
originalResponse: body
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
await next();
|
|
2332
|
-
};
|
|
2333
|
-
return {
|
|
2334
|
-
name: "ResponseValidator",
|
|
2335
|
-
execute: middlewareFn,
|
|
2336
|
-
debug
|
|
2337
|
-
};
|
|
2338
|
-
}
|
|
2339
|
-
function extractZodFieldErrors(error) {
|
|
2340
|
-
if (error instanceof z6.ZodError) {
|
|
2341
|
-
const fieldErrorMap = /* @__PURE__ */ new Map();
|
|
2342
|
-
for (const issue of error.issues) {
|
|
2343
|
-
const fieldPath = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2344
|
-
if (!fieldErrorMap.has(fieldPath)) {
|
|
2345
|
-
fieldErrorMap.set(fieldPath, []);
|
|
2346
|
-
}
|
|
2347
|
-
fieldErrorMap.get(fieldPath).push(issue.message);
|
|
2348
|
-
}
|
|
2349
|
-
return Array.from(fieldErrorMap.entries()).map(([field, messages]) => ({
|
|
2350
|
-
field,
|
|
2351
|
-
messages
|
|
2352
|
-
}));
|
|
2353
|
-
}
|
|
2354
|
-
if (error instanceof Error) {
|
|
2355
|
-
return [{ field: "unknown", messages: [error.message] }];
|
|
2356
|
-
}
|
|
2357
|
-
return [{ field: "unknown", messages: [String(error)] }];
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
// src/router/handlers/executor.ts
|
|
2361
|
-
async function executeHandler(ctx, routeOptions, params) {
|
|
2362
|
-
const middleware = [...routeOptions.middleware || []];
|
|
2363
|
-
if (routeOptions.schema) {
|
|
2364
|
-
if (routeOptions.schema.params || routeOptions.schema.query || routeOptions.schema.body) {
|
|
2365
|
-
middleware.unshift(createRequestValidator(routeOptions.schema));
|
|
2366
|
-
}
|
|
2367
|
-
if (routeOptions.schema.response) {
|
|
2368
|
-
middleware.push(createResponseValidator(routeOptions.schema.response));
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
const handler = compose([...middleware]);
|
|
2372
|
-
await handler(ctx, async () => {
|
|
2373
|
-
const result = await routeOptions.handler(ctx, params);
|
|
2374
|
-
if (!ctx.response.sent && result !== void 0) {
|
|
2375
|
-
ctx.response.json(result);
|
|
2376
|
-
}
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
// src/router/matching/params.ts
|
|
2381
|
-
function extractParams(path6, pattern, paramNames) {
|
|
2382
|
-
const match = pattern.exec(path6);
|
|
2383
|
-
if (!match) {
|
|
2384
|
-
return {};
|
|
2385
|
-
}
|
|
2386
|
-
const params = {};
|
|
2387
|
-
for (let i = 0; i < paramNames.length; i++) {
|
|
2388
|
-
params[paramNames[i]] = match[i + 1] || "";
|
|
2389
|
-
}
|
|
2390
|
-
return params;
|
|
2391
|
-
}
|
|
2392
|
-
function compilePathPattern(path6) {
|
|
2393
|
-
const paramNames = [];
|
|
2394
|
-
if (path6 === "/") {
|
|
2395
|
-
return {
|
|
2396
|
-
pattern: /^\/$/,
|
|
2397
|
-
paramNames: []
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
let patternString = path6.replace(/([.+*?^$(){}|\\])/g, "\\$1");
|
|
2401
|
-
patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
|
|
2402
|
-
paramNames.push(paramName);
|
|
2403
|
-
return "/([^/]+)";
|
|
2404
|
-
}).replace(/\/\[([^\]]+)\]/g, (_, paramName) => {
|
|
2405
|
-
paramNames.push(paramName);
|
|
2406
|
-
return "/([^/]+)";
|
|
2407
|
-
});
|
|
2408
|
-
patternString = `${patternString}(?:/)?`;
|
|
2409
|
-
const pattern = new RegExp(`^${patternString}$`);
|
|
2410
|
-
return {
|
|
2411
|
-
pattern,
|
|
2412
|
-
paramNames
|
|
2413
|
-
};
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
// src/router/matching/matcher.ts
|
|
2417
|
-
function createMatcher() {
|
|
2418
|
-
const routes = [];
|
|
2419
|
-
return {
|
|
2420
|
-
/**
|
|
2421
|
-
* Add a route to the matcher
|
|
2422
|
-
*/
|
|
2423
|
-
add(path6, method, routeOptions) {
|
|
2424
|
-
const { pattern, paramNames } = compilePathPattern(path6);
|
|
2425
|
-
const newRoute = {
|
|
2426
|
-
path: path6,
|
|
2427
|
-
method,
|
|
2428
|
-
pattern,
|
|
2429
|
-
paramNames,
|
|
2430
|
-
routeOptions
|
|
2431
|
-
};
|
|
2432
|
-
const insertIndex = routes.findIndex((route) => paramNames.length < route.paramNames.length);
|
|
2433
|
-
if (insertIndex === -1) {
|
|
2434
|
-
routes.push(newRoute);
|
|
2435
|
-
} else {
|
|
2436
|
-
routes.splice(insertIndex, 0, newRoute);
|
|
2437
|
-
}
|
|
2438
|
-
},
|
|
2439
|
-
/**
|
|
2440
|
-
* Remove a route from the matcher by path
|
|
2441
|
-
*/
|
|
2442
|
-
remove(path6) {
|
|
2443
|
-
for (let i = routes.length - 1; i >= 0; i--) {
|
|
2444
|
-
if (routes[i].path === path6) {
|
|
2445
|
-
routes.splice(i, 1);
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
},
|
|
2449
|
-
/**
|
|
2450
|
-
* Clear all routes from the matcher
|
|
2451
|
-
*/
|
|
2452
|
-
clear() {
|
|
2453
|
-
routes.length = 0;
|
|
2454
|
-
},
|
|
2455
|
-
/**
|
|
2456
|
-
* Match a URL path to a route
|
|
2457
|
-
*/
|
|
2458
|
-
match(path6, method) {
|
|
2459
|
-
const pathname = path6.split("?")[0];
|
|
2460
|
-
if (!pathname) return null;
|
|
2461
|
-
for (const route of routes) {
|
|
2462
|
-
if (route.method !== method) continue;
|
|
2463
|
-
const match = route.pattern.exec(pathname);
|
|
2464
|
-
if (match) {
|
|
2465
|
-
const params = extractParams(path6, route.pattern, route.paramNames);
|
|
2466
|
-
return {
|
|
2467
|
-
route: route.routeOptions,
|
|
2468
|
-
params
|
|
2469
|
-
};
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
const matchingPath = routes.find(
|
|
2473
|
-
(route) => route.method !== method && route.pattern.test(path6)
|
|
2474
|
-
);
|
|
2475
|
-
if (matchingPath) {
|
|
2476
|
-
return {
|
|
2477
|
-
route: null,
|
|
2478
|
-
params: {},
|
|
2479
|
-
methodNotAllowed: true,
|
|
2480
|
-
allowedMethods: routes.filter((route) => route.pattern.test(path6)).map((route) => route.method)
|
|
2481
|
-
};
|
|
2482
|
-
}
|
|
2483
|
-
return null;
|
|
2484
|
-
},
|
|
2485
|
-
/**
|
|
2486
|
-
* Get all registered routes
|
|
2487
|
-
*/
|
|
2488
|
-
getRoutes() {
|
|
2489
|
-
return routes.map((route) => ({
|
|
2490
|
-
path: route.path,
|
|
2491
|
-
method: route.method
|
|
2492
|
-
}));
|
|
2493
|
-
},
|
|
2494
|
-
/**
|
|
2495
|
-
* Find routes matching a specific path
|
|
2496
|
-
*/
|
|
2497
|
-
findRoutes(path6) {
|
|
2498
|
-
return routes.filter((route) => route.pattern.test(path6)).map((route) => ({
|
|
2499
|
-
path: route.path,
|
|
2500
|
-
method: route.method,
|
|
2501
|
-
params: extractParams(path6, route.pattern, route.paramNames)
|
|
2502
|
-
}));
|
|
2503
|
-
}
|
|
2504
|
-
};
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
// src/router/registry/fast-registry.ts
|
|
2508
|
-
function createRouteRegistry() {
|
|
2509
|
-
return {
|
|
2510
|
-
routesByPath: /* @__PURE__ */ new Map(),
|
|
2511
|
-
routesByFile: /* @__PURE__ */ new Map(),
|
|
2512
|
-
pathToFile: /* @__PURE__ */ new Map()
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
function updateRoutesFromFile(registry, filePath, newRoutes) {
|
|
2516
|
-
console.log(`Updating routes from file: ${filePath}`);
|
|
2517
|
-
const oldPaths = registry.routesByFile.get(filePath) || /* @__PURE__ */ new Set();
|
|
2518
|
-
const newPaths = new Set(newRoutes.map((r) => r.path));
|
|
2519
|
-
const added = newRoutes.filter((r) => !oldPaths.has(r.path));
|
|
2520
|
-
const removed = Array.from(oldPaths).filter((p) => !newPaths.has(p));
|
|
2521
|
-
const potentiallyChanged = newRoutes.filter((r) => oldPaths.has(r.path));
|
|
2522
|
-
const changed = potentiallyChanged.filter((route) => {
|
|
2523
|
-
const existingRoute = registry.routesByPath.get(route.path);
|
|
2524
|
-
return !existingRoute || !routesEqual(existingRoute, route);
|
|
2525
|
-
});
|
|
2526
|
-
applyRouteUpdates(registry, filePath, { added, removed, changed });
|
|
2527
|
-
return { added, removed, changed };
|
|
2528
|
-
}
|
|
2529
|
-
function getAllRoutesFromRegistry(registry) {
|
|
2530
|
-
return Array.from(registry.routesByPath.values());
|
|
2531
|
-
}
|
|
2532
|
-
function applyRouteUpdates(registry, filePath, updates) {
|
|
2533
|
-
const { added, removed, changed } = updates;
|
|
2534
|
-
removed.forEach((path6) => {
|
|
2535
|
-
registry.routesByPath.delete(path6);
|
|
2536
|
-
registry.pathToFile.delete(path6);
|
|
2537
|
-
});
|
|
2538
|
-
[...added, ...changed].forEach((route) => {
|
|
2539
|
-
registry.routesByPath.set(route.path, route);
|
|
2540
|
-
registry.pathToFile.set(route.path, filePath);
|
|
2541
|
-
});
|
|
2542
|
-
const allPathsForFile = /* @__PURE__ */ new Set([
|
|
2543
|
-
...added.map((r) => r.path),
|
|
2544
|
-
...changed.map((r) => r.path),
|
|
2545
|
-
...Array.from(registry.routesByFile.get(filePath) || []).filter((p) => !removed.includes(p))
|
|
2546
|
-
]);
|
|
2547
|
-
if (allPathsForFile.size > 0) {
|
|
2548
|
-
registry.routesByFile.set(filePath, allPathsForFile);
|
|
2549
|
-
} else {
|
|
2550
|
-
registry.routesByFile.delete(filePath);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
function routesEqual(route1, route2) {
|
|
2554
|
-
if (route1.path !== route2.path) return false;
|
|
2555
|
-
const methods1 = Object.keys(route1).filter((k) => k !== "path").sort();
|
|
2556
|
-
const methods2 = Object.keys(route2).filter((k) => k !== "path").sort();
|
|
2557
|
-
if (methods1.length !== methods2.length) return false;
|
|
2558
|
-
return methods1.every((method) => {
|
|
2559
|
-
const handler1 = route1[method];
|
|
2560
|
-
const handler2 = route2[method];
|
|
2561
|
-
return typeof handler1 === typeof handler2;
|
|
2562
|
-
});
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2565
|
-
// src/router/utils/matching-helpers.ts
|
|
2566
|
-
function addRouteToMatcher(route, matcher) {
|
|
2567
|
-
Object.entries(route).forEach(([method, methodOptions]) => {
|
|
2568
|
-
if (method === "path" || !methodOptions) return;
|
|
2569
|
-
matcher.add(route.path, method, methodOptions);
|
|
2570
|
-
});
|
|
2571
|
-
}
|
|
2572
|
-
function removeRouteFromMatcher(path6, matcher) {
|
|
2573
|
-
if ("remove" in matcher && typeof matcher.remove === "function") {
|
|
2574
|
-
matcher.remove(path6);
|
|
2575
|
-
} else {
|
|
2576
|
-
console.warn("Matcher does not support selective removal, consider adding remove() method");
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
function updateRouteInMatcher(route, matcher) {
|
|
2580
|
-
removeRouteFromMatcher(route.path, matcher);
|
|
2581
|
-
addRouteToMatcher(route, matcher);
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
// src/router/router.ts
|
|
2585
|
-
var DEFAULT_ROUTER_OPTIONS = {
|
|
2586
|
-
routesDir: "./routes",
|
|
2587
|
-
basePath: "/",
|
|
2588
|
-
watchMode: process.env.NODE_ENV === "development"
|
|
2589
|
-
};
|
|
2590
|
-
function createRouter(options) {
|
|
2591
|
-
const routerOptions = {
|
|
2592
|
-
...DEFAULT_ROUTER_OPTIONS,
|
|
2593
|
-
...options
|
|
2594
|
-
};
|
|
2595
|
-
if (options.basePath && !options.basePath.startsWith("/")) {
|
|
2596
|
-
console.warn("Base path does nothing");
|
|
2597
|
-
}
|
|
2598
|
-
const registry = createRouteRegistry();
|
|
2599
|
-
const matcher = createMatcher();
|
|
2600
|
-
let initialized = false;
|
|
2601
|
-
let initializationPromise = null;
|
|
2602
|
-
let _watchers = null;
|
|
2603
|
-
const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
|
|
2604
|
-
function applyMatcherChanges(changes) {
|
|
2605
|
-
console.log("\n\u{1F527} APPLYING MATCHER CHANGES:");
|
|
2606
|
-
console.log(` Adding ${changes.added.length} routes`);
|
|
2607
|
-
console.log(` Removing ${changes.removed.length} routes`);
|
|
2608
|
-
console.log(` Updating ${changes.changed.length} routes`);
|
|
2609
|
-
changes.removed.forEach((routePath) => {
|
|
2610
|
-
console.log(` \u2796 Removing: ${routePath}`);
|
|
2611
|
-
removeRouteFromMatcher(routePath, matcher);
|
|
2612
|
-
});
|
|
2613
|
-
changes.added.forEach((route) => {
|
|
2614
|
-
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2615
|
-
console.log(` \u2795 Adding: ${route.path} [${methods.join(", ")}]`);
|
|
2616
|
-
addRouteToMatcher(route, matcher);
|
|
2617
|
-
});
|
|
2618
|
-
changes.changed.forEach((route) => {
|
|
2619
|
-
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2620
|
-
console.log(` \u{1F504} Updating: ${route.path} [${methods.join(", ")}]`);
|
|
2621
|
-
updateRouteInMatcher(route, matcher);
|
|
2622
|
-
});
|
|
2623
|
-
console.log("\u2705 Matcher changes applied\n");
|
|
2624
|
-
}
|
|
2625
|
-
function addRoutesWithSource(routes, source) {
|
|
2626
|
-
try {
|
|
2627
|
-
const changes = updateRoutesFromFile(registry, source, routes);
|
|
2628
|
-
applyMatcherChanges(changes);
|
|
2629
|
-
return changes;
|
|
2630
|
-
} catch (error) {
|
|
2631
|
-
console.error(`\u26A0\uFE0F Route conflicts from ${source}:`, error);
|
|
2632
|
-
throw error;
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
async function loadRoutesFromDirectory(directory, source, prefix) {
|
|
2636
|
-
try {
|
|
2637
|
-
const discoveredRoutes = await loadInitialRoutesParallel(directory);
|
|
2638
|
-
const finalRoutes = discoveredRoutes.map(
|
|
2639
|
-
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2640
|
-
);
|
|
2641
|
-
const changes = addRoutesWithSource(finalRoutes, source);
|
|
2642
|
-
console.log(
|
|
2643
|
-
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""} (${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed)`
|
|
2644
|
-
);
|
|
2645
|
-
} catch (error) {
|
|
2646
|
-
console.error(`\u26A0\uFE0F Failed to load routes from ${source}:`, error);
|
|
2647
|
-
throw error;
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
async function initialize() {
|
|
2651
|
-
if (initialized || initializationPromise) {
|
|
2652
|
-
return initializationPromise;
|
|
2653
|
-
}
|
|
2654
|
-
initializationPromise = (async () => {
|
|
2655
|
-
try {
|
|
2656
|
-
await Promise.all(
|
|
2657
|
-
Array.from(routeDirectories).map(
|
|
2658
|
-
(directory) => loadRoutesFromDirectory(directory, directory)
|
|
2659
|
-
)
|
|
2660
|
-
);
|
|
2661
|
-
if (routerOptions.watchMode) {
|
|
2662
|
-
setupOptimizedWatching();
|
|
2663
|
-
}
|
|
2664
|
-
initialized = true;
|
|
2665
|
-
} catch (error) {
|
|
2666
|
-
console.error("\u26A0\uFE0F Failed to initialize router:", error);
|
|
2667
|
-
throw error;
|
|
2668
|
-
}
|
|
2669
|
-
})();
|
|
2670
|
-
return initializationPromise;
|
|
2671
|
-
}
|
|
2672
|
-
function setupOptimizedWatching() {
|
|
2673
|
-
if (!_watchers) {
|
|
2674
|
-
_watchers = /* @__PURE__ */ new Map();
|
|
2675
|
-
}
|
|
2676
|
-
for (const directory of routeDirectories) {
|
|
2677
|
-
if (!_watchers.has(directory)) {
|
|
2678
|
-
const watcher = watchRoutes(directory, {
|
|
2679
|
-
debounceMs: 16,
|
|
2680
|
-
// ~60fps debouncing
|
|
2681
|
-
ignore: ["node_modules", ".git"],
|
|
2682
|
-
onRouteAdded: (filepath, addedRoutes) => {
|
|
2683
|
-
try {
|
|
2684
|
-
const changes = updateRoutesFromFile(registry, filepath, addedRoutes);
|
|
2685
|
-
applyMatcherChanges(changes);
|
|
2686
|
-
} catch (error) {
|
|
2687
|
-
console.error(`Error adding routes from ${directory}:`, error);
|
|
2688
|
-
}
|
|
2689
|
-
},
|
|
2690
|
-
onRouteChanged: withPerformanceTracking(
|
|
2691
|
-
async (filepath, changedRoutes) => {
|
|
2692
|
-
try {
|
|
2693
|
-
console.log(`Processing changes for ${filepath}`);
|
|
2694
|
-
const changes = updateRoutesFromFile(registry, filepath, changedRoutes);
|
|
2695
|
-
console.log(
|
|
2696
|
-
`Changes detected: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2697
|
-
);
|
|
2698
|
-
applyMatcherChanges(changes);
|
|
2699
|
-
console.log(
|
|
2700
|
-
`Route changes applied: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2701
|
-
);
|
|
2702
|
-
} catch (error) {
|
|
2703
|
-
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
2704
|
-
}
|
|
2705
|
-
},
|
|
2706
|
-
directory
|
|
2707
|
-
),
|
|
2708
|
-
onRouteRemoved: (filePath, removedRoutes) => {
|
|
2709
|
-
console.log(`File removed: ${filePath} with ${removedRoutes.length} routes`);
|
|
2710
|
-
try {
|
|
2711
|
-
removedRoutes.forEach((route) => {
|
|
2712
|
-
removeRouteFromMatcher(route.path, matcher);
|
|
2713
|
-
});
|
|
2714
|
-
clearFileCache(filePath);
|
|
2715
|
-
} catch (error) {
|
|
2716
|
-
console.error(`\u26A0\uFE0F Error removing routes from ${filePath}:`, error);
|
|
2717
|
-
}
|
|
2718
|
-
},
|
|
2719
|
-
onError: (error) => {
|
|
2720
|
-
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
2721
|
-
}
|
|
2722
|
-
});
|
|
2723
|
-
_watchers.set(directory, watcher);
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
function setupWatcherForNewDirectory(directory, prefix) {
|
|
2728
|
-
if (!_watchers) {
|
|
2729
|
-
_watchers = /* @__PURE__ */ new Map();
|
|
2730
|
-
}
|
|
2731
|
-
const watcher = watchRoutes(directory, {
|
|
2732
|
-
debounceMs: 16,
|
|
2733
|
-
ignore: ["node_modules", ".git"],
|
|
2734
|
-
onRouteAdded: (filePath, addedRoutes) => {
|
|
2735
|
-
try {
|
|
2736
|
-
const finalRoutes = addedRoutes.map(
|
|
2737
|
-
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2738
|
-
);
|
|
2739
|
-
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
2740
|
-
applyMatcherChanges(changes);
|
|
2741
|
-
} catch (error) {
|
|
2742
|
-
console.error(`\u26A0\uFE0F Error adding routes from ${directory}:`, error);
|
|
2743
|
-
}
|
|
2744
|
-
},
|
|
2745
|
-
onRouteChanged: withPerformanceTracking(async (filePath, changedRoutes) => {
|
|
2746
|
-
try {
|
|
2747
|
-
const finalRoutes = changedRoutes.map(
|
|
2748
|
-
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2749
|
-
);
|
|
2750
|
-
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
2751
|
-
applyMatcherChanges(changes);
|
|
2752
|
-
} catch (error) {
|
|
2753
|
-
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
2754
|
-
}
|
|
2755
|
-
}, directory),
|
|
2756
|
-
onRouteRemoved: (filePath, removedRoutes) => {
|
|
2757
|
-
try {
|
|
2758
|
-
removedRoutes.forEach((route) => {
|
|
2759
|
-
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
2760
|
-
removeRouteFromMatcher(finalPath, matcher);
|
|
2761
|
-
});
|
|
2762
|
-
clearFileCache(filePath);
|
|
2763
|
-
} catch (error) {
|
|
2764
|
-
console.error(`Error removing routes from ${filePath}:`, error);
|
|
2765
|
-
}
|
|
2766
|
-
},
|
|
2767
|
-
onError: (error) => {
|
|
2768
|
-
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
2769
|
-
}
|
|
2770
|
-
});
|
|
2771
|
-
_watchers.set(directory, watcher);
|
|
2772
|
-
return watcher;
|
|
2773
|
-
}
|
|
2774
|
-
initialize().catch((error) => {
|
|
2775
|
-
console.error("\u26A0\uFE0F Failed to initialize router on creation:", error);
|
|
2776
|
-
});
|
|
2777
|
-
return {
|
|
2778
|
-
/**
|
|
2779
|
-
* Handle an incoming request
|
|
2780
|
-
*/
|
|
2781
|
-
async handleRequest(ctx) {
|
|
2782
|
-
if (!initialized) {
|
|
2783
|
-
console.log("\u{1F504} Router not initialized, initializing...");
|
|
2784
|
-
await initialize();
|
|
2785
|
-
}
|
|
2786
|
-
const { method, path: path6 } = ctx.request;
|
|
2787
|
-
console.log(`
|
|
2788
|
-
\u{1F4E5} Handling request: ${method} ${path6}`);
|
|
2789
|
-
const match = matcher.match(path6, method);
|
|
2790
|
-
if (!match) {
|
|
2791
|
-
console.log(`\u274C No match found for: ${method} ${path6}`);
|
|
2792
|
-
throw new NotFoundError("Not found");
|
|
2793
|
-
}
|
|
2794
|
-
console.log(`\u2705 Route matched: ${method} ${path6}`);
|
|
2795
|
-
console.log(` Params: ${JSON.stringify(match.params)}`);
|
|
2796
|
-
if (match.methodNotAllowed) {
|
|
2797
|
-
ctx.response.status(405).json({
|
|
2798
|
-
error: "\u274C Method Not Allowed",
|
|
2799
|
-
allowed: match.allowedMethods
|
|
2800
|
-
});
|
|
2801
|
-
if (match.allowedMethods && match.allowedMethods.length > 0) {
|
|
2802
|
-
ctx.response.header("Allow", match.allowedMethods.join(", "));
|
|
2803
|
-
}
|
|
2804
|
-
return;
|
|
2805
|
-
}
|
|
2806
|
-
ctx.request.params = match.params;
|
|
2807
|
-
await executeHandler(ctx, match.route, match.params);
|
|
2808
|
-
},
|
|
2809
|
-
/**
|
|
2810
|
-
* Get all registered routes (using optimized registry)
|
|
2811
|
-
*/
|
|
2812
|
-
getRoutes() {
|
|
2813
|
-
return getAllRoutesFromRegistry(registry);
|
|
2814
|
-
},
|
|
2815
|
-
/**
|
|
2816
|
-
* Add a route programmatically
|
|
2817
|
-
*/
|
|
2818
|
-
addRoute(route) {
|
|
2819
|
-
const changes = updateRoutesFromFile(registry, "programmatic", [route]);
|
|
2820
|
-
applyMatcherChanges(changes);
|
|
2821
|
-
},
|
|
2822
|
-
/**
|
|
2823
|
-
* Add multiple routes programmatically with batch processing
|
|
2824
|
-
*/
|
|
2825
|
-
addRoutes(routes) {
|
|
2826
|
-
const changes = updateRoutesFromFile(registry, "programmatic", routes);
|
|
2827
|
-
applyMatcherChanges(changes);
|
|
2828
|
-
return changes;
|
|
2829
|
-
},
|
|
2830
|
-
/**
|
|
2831
|
-
* Add a route directory (for plugins) with optimized loading
|
|
2832
|
-
*/
|
|
2833
|
-
async addRouteDirectory(directory, options2 = {}) {
|
|
2834
|
-
if (routeDirectories.has(directory)) {
|
|
2835
|
-
console.warn(`Route directory ${directory} already registered`);
|
|
2836
|
-
return;
|
|
2837
|
-
}
|
|
2838
|
-
routeDirectories.add(directory);
|
|
2839
|
-
if (initialized) {
|
|
2840
|
-
await loadRoutesFromDirectory(directory, directory, options2.prefix);
|
|
2841
|
-
if (routerOptions.watchMode) {
|
|
2842
|
-
setupWatcherForNewDirectory(directory, options2.prefix);
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
},
|
|
2846
|
-
/**
|
|
2847
|
-
* Get route conflicts (using registry)
|
|
2848
|
-
*/
|
|
2849
|
-
getRouteConflicts() {
|
|
2850
|
-
const conflicts = [];
|
|
2851
|
-
return conflicts;
|
|
2852
|
-
},
|
|
2853
|
-
/**
|
|
2854
|
-
* Close watchers and cleanup (useful for testing)
|
|
2855
|
-
*/
|
|
2856
|
-
async close() {
|
|
2857
|
-
if (_watchers) {
|
|
2858
|
-
for (const watcher of _watchers.values()) {
|
|
2859
|
-
await watcher.close();
|
|
2860
|
-
}
|
|
2861
|
-
_watchers.clear();
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
};
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
// src/server/create.ts
|
|
2868
|
-
var DEFAULT_OPTIONS2 = {
|
|
2869
|
-
port: 3e3,
|
|
2870
|
-
host: "localhost",
|
|
2871
|
-
routesDir: "./routes",
|
|
2872
|
-
http2: {
|
|
2873
|
-
enabled: true
|
|
2874
|
-
},
|
|
2875
|
-
middleware: [],
|
|
2876
|
-
plugins: []
|
|
2877
|
-
};
|
|
2878
|
-
function createServerOptions(options = {}) {
|
|
2879
|
-
const baseOptions = { ...DEFAULT_OPTIONS2 };
|
|
2880
|
-
setRuntimeConfig({ routesDir: options.routesDir || baseOptions.routesDir });
|
|
2881
|
-
return {
|
|
2882
|
-
port: options.port ?? baseOptions.port,
|
|
2883
|
-
host: options.host ?? baseOptions.host,
|
|
2884
|
-
routesDir: options.routesDir ?? baseOptions.routesDir,
|
|
2885
|
-
http2: {
|
|
2886
|
-
enabled: options.http2?.enabled ?? baseOptions.http2?.enabled,
|
|
2887
|
-
keyFile: options.http2?.keyFile ?? baseOptions.http2?.keyFile,
|
|
2888
|
-
certFile: options.http2?.certFile ?? baseOptions.http2?.certFile
|
|
2889
|
-
},
|
|
2890
|
-
middleware: [...baseOptions.middleware || [], ...options.middleware || []],
|
|
2891
|
-
plugins: [...baseOptions.plugins || [], ...options.plugins || []]
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
|
-
function createListenMethod(serverInstance, validatedOptions, initialMiddleware, initialPlugins) {
|
|
2895
|
-
return async () => {
|
|
2896
|
-
await initializeComponents(serverInstance, initialMiddleware, initialPlugins);
|
|
2897
|
-
await serverInstance.pluginManager.initializePlugins(serverInstance);
|
|
2898
|
-
await startServer(serverInstance, validatedOptions);
|
|
2899
|
-
await serverInstance.pluginManager.onServerStart(serverInstance, serverInstance.server);
|
|
2900
|
-
setupServerLifecycle(serverInstance);
|
|
2901
|
-
return serverInstance;
|
|
2902
|
-
};
|
|
2903
|
-
}
|
|
2904
|
-
async function initializeComponents(serverInstance, initialMiddleware, initialPlugins) {
|
|
2905
|
-
for (const mw of initialMiddleware) {
|
|
2906
|
-
serverInstance.use(mw);
|
|
2907
|
-
}
|
|
2908
|
-
for (const p of initialPlugins) {
|
|
2909
|
-
await serverInstance.register(p);
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
function setupServerLifecycle(serverInstance) {
|
|
2913
|
-
const signalHandlers = registerSignalHandlers(() => serverInstance.close());
|
|
2914
|
-
serverInstance._signalHandlers = signalHandlers;
|
|
2915
|
-
serverInstance.events.emit("started");
|
|
2916
|
-
}
|
|
2917
|
-
function createCloseMethod(serverInstance) {
|
|
2918
|
-
return async (stopOptions) => {
|
|
2919
|
-
if (!serverInstance.server) {
|
|
2920
|
-
return;
|
|
2921
|
-
}
|
|
2922
|
-
const options = { ...stopOptions };
|
|
2923
|
-
if (serverInstance._signalHandlers) {
|
|
2924
|
-
serverInstance._signalHandlers.unregister();
|
|
2925
|
-
delete serverInstance._signalHandlers;
|
|
2926
|
-
}
|
|
2927
|
-
await stopServer(serverInstance, options);
|
|
2928
|
-
};
|
|
2929
|
-
}
|
|
2930
|
-
function createUseMethod(serverInstance) {
|
|
2931
|
-
return (middleware) => {
|
|
2932
|
-
const middlewareArray = Array.isArray(middleware) ? middleware : [middleware];
|
|
2933
|
-
serverInstance.middleware.push(...middlewareArray);
|
|
2934
|
-
return serverInstance;
|
|
2935
|
-
};
|
|
2936
|
-
}
|
|
2937
|
-
function createRegisterMethod(serverInstance) {
|
|
2938
|
-
return async (plugin) => {
|
|
2939
|
-
validatePlugin(plugin);
|
|
2940
|
-
serverInstance.plugins.push(plugin);
|
|
2941
|
-
await plugin.register(serverInstance);
|
|
2942
|
-
return serverInstance;
|
|
2943
|
-
};
|
|
2944
|
-
}
|
|
2945
|
-
function create3(options = {}) {
|
|
2946
|
-
const mergedOptions = createServerOptions(options);
|
|
2947
|
-
let validatedOptions;
|
|
2948
|
-
try {
|
|
2949
|
-
validatedOptions = validateServerOptions(mergedOptions);
|
|
2950
|
-
} catch (error) {
|
|
2951
|
-
if (error instanceof Error) {
|
|
2952
|
-
throw new Error(`Failed to create server: ${error.message}`);
|
|
2953
|
-
}
|
|
2954
|
-
throw new Error(`Failed to create server: ${String(error)}`);
|
|
2955
|
-
}
|
|
2956
|
-
const { port, host, middleware, plugins } = validatedOptions;
|
|
2957
|
-
const initialMiddleware = Array.isArray(middleware) ? [...middleware] : [];
|
|
2958
|
-
const initialPlugins = Array.isArray(plugins) ? [...plugins] : [];
|
|
2959
|
-
const contextStorage2 = new AsyncLocalStorage2();
|
|
2960
|
-
const router = createRouter({
|
|
2961
|
-
routesDir: validatedOptions.routesDir,
|
|
2962
|
-
watchMode: process.env.NODE_ENV === "development"
|
|
2963
|
-
});
|
|
2964
|
-
const pluginManager = createPluginLifecycleManager({
|
|
2965
|
-
debug: process.env.NODE_ENV === "development",
|
|
2966
|
-
continueOnError: true
|
|
2967
|
-
});
|
|
2968
|
-
const events = new EventEmitter();
|
|
2969
|
-
const serverInstance = {
|
|
2970
|
-
server: null,
|
|
2971
|
-
port,
|
|
2972
|
-
host,
|
|
2973
|
-
context: contextStorage2,
|
|
2974
|
-
events,
|
|
2975
|
-
plugins: [],
|
|
2976
|
-
middleware: [],
|
|
2977
|
-
_signalHandlers: { unregister: () => {
|
|
2978
|
-
} },
|
|
2979
|
-
use: () => serverInstance,
|
|
2980
|
-
register: async () => serverInstance,
|
|
2981
|
-
listen: async () => serverInstance,
|
|
2982
|
-
close: async () => {
|
|
2983
|
-
},
|
|
2984
|
-
router,
|
|
2985
|
-
pluginManager
|
|
2986
|
-
};
|
|
2987
|
-
serverInstance.listen = createListenMethod(
|
|
2988
|
-
serverInstance,
|
|
2989
|
-
validatedOptions,
|
|
2990
|
-
initialMiddleware,
|
|
2991
|
-
initialPlugins
|
|
2992
|
-
);
|
|
2993
|
-
serverInstance.close = createCloseMethod(serverInstance);
|
|
2994
|
-
serverInstance.use = createUseMethod(serverInstance);
|
|
2995
|
-
serverInstance.register = createRegisterMethod(serverInstance);
|
|
2996
|
-
return serverInstance;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
// src/errors/unauthorized-error.ts
|
|
3000
|
-
var UnauthorizedError = class extends BlaizeError {
|
|
3001
|
-
/**
|
|
3002
|
-
* Creates a new UnauthorizedError instance
|
|
3003
|
-
*
|
|
3004
|
-
* @param title - Human-readable error message
|
|
3005
|
-
* @param details - Optional authentication context
|
|
3006
|
-
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3007
|
-
*/
|
|
3008
|
-
constructor(title, details = void 0, correlationId = void 0) {
|
|
3009
|
-
super(
|
|
3010
|
-
"UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
3011
|
-
title,
|
|
3012
|
-
401,
|
|
3013
|
-
// HTTP 401 Unauthorized
|
|
3014
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3015
|
-
details
|
|
3016
|
-
);
|
|
3017
|
-
}
|
|
3018
|
-
};
|
|
3019
|
-
|
|
3020
|
-
// src/errors/forbidden-error.ts
|
|
3021
|
-
var ForbiddenError = class extends BlaizeError {
|
|
3022
|
-
/**
|
|
3023
|
-
* Creates a new ForbiddenError instance
|
|
3024
|
-
*
|
|
3025
|
-
* @param title - Human-readable error message
|
|
3026
|
-
* @param details - Optional permission context
|
|
3027
|
-
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3028
|
-
*/
|
|
3029
|
-
constructor(title, details = void 0, correlationId = void 0) {
|
|
3030
|
-
super(
|
|
3031
|
-
"FORBIDDEN" /* FORBIDDEN */,
|
|
3032
|
-
title,
|
|
3033
|
-
403,
|
|
3034
|
-
// HTTP 403 Forbidden
|
|
3035
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3036
|
-
details
|
|
3037
|
-
);
|
|
3038
|
-
}
|
|
3039
|
-
};
|
|
3040
|
-
|
|
3041
|
-
// src/errors/conflict-error.ts
|
|
3042
|
-
var ConflictError = class extends BlaizeError {
|
|
3043
|
-
/**
|
|
3044
|
-
* Creates a new ConflictError instance
|
|
3045
|
-
*
|
|
3046
|
-
* @param title - Human-readable error message
|
|
3047
|
-
* @param details - Optional conflict context
|
|
3048
|
-
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3049
|
-
*/
|
|
3050
|
-
constructor(title, details = void 0, correlationId = void 0) {
|
|
3051
|
-
super(
|
|
3052
|
-
"CONFLICT" /* CONFLICT */,
|
|
3053
|
-
title,
|
|
3054
|
-
409,
|
|
3055
|
-
// HTTP 409 Conflict
|
|
3056
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3057
|
-
details
|
|
3058
|
-
);
|
|
3059
|
-
}
|
|
3060
|
-
};
|
|
3061
|
-
|
|
3062
|
-
// src/errors/rate-limit-error.ts
|
|
3063
|
-
var RateLimitError = class extends BlaizeError {
|
|
3064
|
-
/**
|
|
3065
|
-
* Creates a new RateLimitError instance
|
|
3066
|
-
*
|
|
3067
|
-
* @param title - Human-readable error message
|
|
3068
|
-
* @param details - Optional rate limit context
|
|
3069
|
-
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3070
|
-
*/
|
|
3071
|
-
constructor(title, details = void 0, correlationId = void 0) {
|
|
3072
|
-
super(
|
|
3073
|
-
"RATE_LIMITED" /* RATE_LIMITED */,
|
|
3074
|
-
title,
|
|
3075
|
-
429,
|
|
3076
|
-
// HTTP 429 Too Many Requests
|
|
3077
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3078
|
-
details
|
|
3079
|
-
);
|
|
3080
|
-
}
|
|
3081
|
-
};
|
|
3082
|
-
|
|
3083
|
-
// src/errors/request-timeout-error.ts
|
|
3084
|
-
var RequestTimeoutError = class extends BlaizeError {
|
|
3085
|
-
constructor(title, details, correlationId) {
|
|
3086
|
-
super(
|
|
3087
|
-
"UPLOAD_TIMEOUT" /* UPLOAD_TIMEOUT */,
|
|
3088
|
-
title,
|
|
3089
|
-
408,
|
|
3090
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3091
|
-
details
|
|
3092
|
-
);
|
|
3093
|
-
}
|
|
3094
|
-
};
|
|
3095
|
-
|
|
3096
|
-
// src/errors/unprocessable-entity-error.ts
|
|
3097
|
-
var UnprocessableEntityError = class extends BlaizeError {
|
|
3098
|
-
constructor(title, details, correlationId) {
|
|
3099
|
-
super(
|
|
3100
|
-
"UNPROCESSABLE_ENTITY" /* UNPROCESSABLE_ENTITY */,
|
|
3101
|
-
title,
|
|
3102
|
-
422,
|
|
3103
|
-
correlationId ?? getCurrentCorrelationId(),
|
|
3104
|
-
details
|
|
3105
|
-
);
|
|
3106
|
-
}
|
|
3107
|
-
};
|
|
3108
|
-
|
|
3109
|
-
// src/index.ts
|
|
3110
|
-
var VERSION = "0.1.0";
|
|
3111
|
-
var ServerAPI = { createServer: create3 };
|
|
3112
|
-
var RouterAPI = {
|
|
3113
|
-
createDeleteRoute,
|
|
3114
|
-
createGetRoute,
|
|
3115
|
-
createHeadRoute,
|
|
3116
|
-
createOptionsRoute,
|
|
3117
|
-
createPatchRoute,
|
|
3118
|
-
createPostRoute,
|
|
3119
|
-
createPutRoute
|
|
3120
|
-
};
|
|
3121
|
-
var MiddlewareAPI = { createMiddleware: create, compose };
|
|
3122
|
-
var PluginsAPI = { createPlugin: create2 };
|
|
3123
|
-
var Blaize = {
|
|
3124
|
-
// Core functions
|
|
3125
|
-
createServer: create3,
|
|
3126
|
-
createMiddleware: create,
|
|
3127
|
-
createPlugin: create2,
|
|
3128
|
-
// Namespaces (using the non-conflicting names)
|
|
3129
|
-
Server: ServerAPI,
|
|
3130
|
-
Router: RouterAPI,
|
|
3131
|
-
Middleware: MiddlewareAPI,
|
|
3132
|
-
Plugins: PluginsAPI,
|
|
3133
|
-
// Constants
|
|
3134
|
-
VERSION
|
|
3135
|
-
};
|
|
3136
|
-
var index_default = Blaize;
|
|
3137
|
-
export {
|
|
3138
|
-
Blaize,
|
|
3139
|
-
BlaizeError,
|
|
3140
|
-
ConflictError,
|
|
3141
|
-
ErrorSeverity,
|
|
3142
|
-
ErrorType,
|
|
3143
|
-
ForbiddenError,
|
|
3144
|
-
InternalServerError,
|
|
3145
|
-
MiddlewareAPI,
|
|
3146
|
-
NotFoundError,
|
|
3147
|
-
PayloadTooLargeError,
|
|
3148
|
-
PluginsAPI,
|
|
3149
|
-
RateLimitError,
|
|
3150
|
-
RequestTimeoutError,
|
|
3151
|
-
RouterAPI,
|
|
3152
|
-
ServerAPI,
|
|
3153
|
-
UnauthorizedError,
|
|
3154
|
-
UnprocessableEntityError,
|
|
3155
|
-
UnsupportedMediaTypeError,
|
|
3156
|
-
VERSION,
|
|
3157
|
-
ValidationError,
|
|
3158
|
-
compose,
|
|
3159
|
-
createDeleteRoute,
|
|
3160
|
-
createGetRoute,
|
|
3161
|
-
createHeadRoute,
|
|
3162
|
-
create as createMiddleware,
|
|
3163
|
-
createOptionsRoute,
|
|
3164
|
-
createPatchRoute,
|
|
3165
|
-
create2 as createPlugin,
|
|
3166
|
-
createPostRoute,
|
|
3167
|
-
createPutRoute,
|
|
3168
|
-
create3 as createServer,
|
|
3169
|
-
index_default as default,
|
|
3170
|
-
isBodyParseError
|
|
3171
|
-
};
|
|
27
|
+
`),n()}),e.on("error",s=>{console.error("Server error:",s),i(s)})})}async function Fr(e){for(let t of e.plugins)typeof t.initialize=="function"&&await t.initialize(e)}async function at(e,t){if(!e.server)try{let r=t.port,o=t.host;await Fr(e);let n=t.http2||{enabled:!0},i=!!n.enabled,s=await vr(n);t.http2&&s.keyFile&&s.certFile&&(t.http2.keyFile=s.keyFile,t.http2.certFile=s.certFile);let a=Er(i,s);e.server=a,e.port=r,e.host=o;let u=nt(e);a.on("request",u),await Cr(a,r,o,i)}catch(r){throw console.error("Failed to start server:",r),r}}var X=!1;async function ut(e,t={}){let r=e.server,o=e.events;if(X){console.log("\u26A0\uFE0F Shutdown already in progress, ignoring duplicate shutdown request");return}if(!r)return;X=!0;let n=t.timeout||5e3;try{if(t.onStopping&&await t.onStopping(),o.emit("stopping"),e.router&&typeof e.router.close=="function"){console.log("\u{1F50C} Closing router watchers...");try{await Promise.race([e.router.close(),new Promise((a,u)=>setTimeout(()=>u(new Error("Router close timeout")),2e3))]),console.log("\u2705 Router watchers closed")}catch(a){console.error("\u274C Error closing router watchers:",a)}}try{await Promise.race([e.pluginManager.onServerStop(e,r),new Promise((a,u)=>setTimeout(()=>u(new Error("Plugin stop timeout")),2e3))])}catch(a){console.error("\u274C Plugin stop timeout:",a)}let i=new Promise((a,u)=>{r.close(c=>{if(c)return u(c);a()})}),s=new Promise((a,u)=>{setTimeout(()=>{u(new Error("Server shutdown timeout"))},n)});await Promise.race([i,s]);try{await Promise.race([e.pluginManager.terminatePlugins(e),new Promise((a,u)=>setTimeout(()=>u(new Error("Plugin terminate timeout")),1e3))])}catch(a){console.error("\u274C Plugin terminate timeout:",a)}t.onStopped&&await t.onStopped(),o.emit("stopped"),e.server=null,console.log("\u2705 Graceful shutdown completed"),X=!1}catch(i){throw X=!1,console.error("\u26A0\uFE0F Shutdown error (forcing exit):",i),r&&typeof r.close=="function"&&r.close(),process.env.NODE_ENV==="development"&&(console.log("\u{1F504} Forcing exit for development restart..."),process.exit(0)),o.emit("error",i),i}}function lt(e){if(process.env.NODE_ENV==="development"){let r=()=>{console.log("\u{1F4E4} SIGINT received, forcing exit for development restart..."),process.exit(0)},o=()=>{console.log("\u{1F4E4} SIGTERM received, forcing exit for development restart..."),process.exit(0)};return process.on("SIGINT",r),process.on("SIGTERM",o),{unregister:()=>{process.removeListener("SIGINT",r),process.removeListener("SIGTERM",o)}}}else{let r=()=>{console.log("\u{1F4E4} SIGINT received, starting graceful shutdown..."),e().catch(console.error)},o=()=>{console.log("\u{1F4E4} SIGTERM received, starting graceful shutdown..."),e().catch(console.error)};return process.on("SIGINT",r),process.on("SIGTERM",o),{unregister:()=>{process.removeListener("SIGINT",r),process.removeListener("SIGTERM",o)}}}}import{z as b}from"zod";var Mr=b.custom(e=>e!==null&&typeof e=="object"&&"execute"in e&&typeof e.execute=="function",{message:"Expected middleware to have an execute function"}),zr=b.custom(e=>e!==null&&typeof e=="object"&&"register"in e&&typeof e.register=="function",{message:"Expected a valid plugin object with a register method"}),Br=b.object({enabled:b.boolean().optional().default(!0),keyFile:b.string().optional(),certFile:b.string().optional()}).refine(e=>e.enabled&&process.env.NODE_ENV==="production"?e.keyFile&&e.certFile:!0,{message:"When HTTP/2 is enabled (outside of development mode), both keyFile and certFile must be provided"}),$r=b.object({port:b.number().int().positive().optional().default(3e3),host:b.string().optional().default("localhost"),routesDir:b.string().optional().default("./routes"),http2:Br.optional().default({enabled:!0}),middleware:b.array(Mr).optional().default([]),plugins:b.array(zr).optional().default([])});function ct(e){try{return $r.parse(e)}catch(t){if(t instanceof b.ZodError){let r=t.format();throw new Error(`Invalid server options: ${JSON.stringify(r,null,2)}`)}throw new Error(`Invalid server options: ${String(t)}`)}}function dt(e={}){let{continueOnError:t=!0,debug:r=!1,onError:o}=e;function n(s,...a){r&&console.log(`[PluginLifecycle] ${s}`,...a)}function i(s,a,u){let c=`Plugin ${s.name} failed during ${a}: ${u.message}`;if(o?o(s,a,u):console.error(c,u),!t)throw new Error(c)}return{async initializePlugins(s){n("Initializing plugins...");for(let a of s.plugins)if(a.initialize)try{n(`Initializing plugin: ${a.name}`),await a.initialize(s)}catch(u){i(a,"initialize",u)}n(`Initialized ${s.plugins.length} plugins`)},async terminatePlugins(s){n("Terminating plugins...");let a=[...s.plugins].reverse();for(let u of a)if(u.terminate)try{n(`Terminating plugin: ${u.name}`),await u.terminate(s)}catch(c){i(u,"terminate",c)}n(`Terminated ${a.length} plugins`)},async onServerStart(s,a){n("Notifying plugins of server start...");for(let u of s.plugins)if(u.onServerStart)try{n(`Notifying plugin of server start: ${u.name}`),await u.onServerStart(a)}catch(c){i(u,"onServerStart",c)}},async onServerStop(s,a){n("Notifying plugins of server stop...");let u=[...s.plugins].reverse();for(let c of u)if(c.onServerStop)try{n(`Notifying plugin of server stop: ${c.name}`),await c.onServerStop(a)}catch(w){i(c,"onServerStop",w)}}}}var P=class extends Error{constructor(r,o){super(`Plugin validation error${r?` for "${r}"`:""}: ${o}`);this.pluginName=r;this.name="PluginValidationError"}};var Or=new Set(["core","server","router","middleware","context","blaize","blaizejs"]),kr=/^[a-z]([a-z0-9-]*[a-z0-9])?$/,Dr=/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;function pt(e,t={}){let{requireVersion:r=!0,validateNameFormat:o=!0,checkReservedNames:n=!0}=t;if(!e||typeof e!="object")throw new P("","Plugin must be an object");let i=e;if(!i.name||typeof i.name!="string")throw new P("","Plugin must have a name (string)");if(o&&!kr.test(i.name))throw new P(i.name,"Plugin name must be lowercase letters, numbers, and hyphens only");if(n&&Or.has(i.name.toLowerCase()))throw new P(i.name,`Plugin name "${i.name}" is reserved`);if(r){if(!i.version||typeof i.version!="string")throw new P(i.name,"Plugin must have a version (string)");if(!Dr.test(i.version))throw new P(i.name,'Plugin version must follow semantic versioning (e.g., "1.0.0")')}if(!i.register||typeof i.register!="function")throw new P(i.name,"Plugin must have a register method (function)");let s=["initialize","terminate","onServerStart","onServerStop"];for(let a of s)if(i[a]&&typeof i[a]!="function")throw new P(i.name,`Plugin ${a} must be a function if provided`)}import*as mt from"node:crypto";import*as gt from"node:fs/promises";import{createRequire as Hr}from"node:module";import*as yt from"node:path";async function Nr(e){let t=`?t=${Date.now()}`,r=e+t;try{let o=await import(r);return console.log("\u2705 Successfully imported module"),o}catch(o){let n=o instanceof Error?o.message:String(o);return console.log("\u26A0\uFE0F Error importing with cache buster, trying original path:",n),import(e)}}async function ft(e,t){try{let r=W(e,t),o=await Nr(e);console.log("\u{1F4E6} Module exports:",Object.keys(o));let n=[];if(o.default&&typeof o.default=="object"){let i={...o.default,path:r.routePath};n.push(i)}return Object.entries(o).forEach(([i,s])=>{if(i==="default"||!s||typeof s!="object")return;let a=s;if(Ar(a)){let u={...a,path:r.routePath};n.push(u)}}),n.length===0?(console.warn(`Route file ${e} does not export any valid route definitions`),[]):(console.log(`\u2705 Successfully Loaded ${n.length} route(s)`),n)}catch(r){return console.error(`Failed to load route module ${e}:`,r),[]}}function Ar(e){return!e||typeof e!="object"?!1:["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"].some(o=>e[o]&&typeof e[o]=="object"&&e[o].handler)}var j=new Map;async function _(e,t,r=!0){let n=(await gt.stat(e)).mtime.getTime(),i=j.get(e);if(r&&i&&i.timestamp===n)return i.routes;Ur(e);let s=await ft(e,t);if(r){let a=wt(s);j.set(e,{routes:s,timestamp:n,hash:a})}return s}function ht(e,t){let r=j.get(e);if(!r)return!0;let o=wt(t);return r.hash!==o}function ue(e){e?j.delete(e):j.clear()}function wt(e){let t=e.map(n=>({path:n.path,methods:Object.keys(n).filter(i=>i!=="path").sort().map(i=>{let s=n[i],a=s?.handler?s.handler.toString():null;return{method:i,handler:a,middleware:s?.middleware?s.middleware.length:0,hasSchema:!!s?.schema,schemaKeys:s?.schema?Object.keys(s.schema).sort():[]}})})),r=JSON.stringify(t);return mt.createHash("md5").update(r).digest("hex")}function Ur(e){try{let t=yt.resolve(e);if(typeof A<"u"){delete A.cache[t];try{let r=A.resolve(t);delete A.cache[r]}catch(r){let o=r instanceof Error?r.message:String(r);console.log(`\u26A0\uFE0F Could not resolve path: ${o}`)}}else try{let r=Hr(import.meta.url);delete r.cache[t];try{let o=r.resolve(t);delete r.cache[o]}catch{console.log("\u26A0\uFE0F Could not resolve ESM path")}}catch{console.log("\u26A0\uFE0F createRequire not available in pure ESM")}}catch(t){console.log(`\u26A0\uFE0F Error during module cache invalidation for ${e}:`,t)}}import*as Rt from"node:os";import*as K from"node:fs/promises";import*as N from"node:path";async function ee(e,t={}){let r=N.isAbsolute(e)?e:N.resolve(process.cwd(),e);console.log("Creating router with routes directory:",r);try{if(!(await K.stat(r)).isDirectory())throw new Error(`Route directory is not a directory: ${r}`)}catch(s){throw s.code==="ENOENT"?new Error(`Route directory not found: ${r}`):s}let o=[],n=t.ignore||["node_modules",".git"];async function i(s){let a=await K.readdir(s,{withFileTypes:!0});for(let u of a){let c=N.join(s,u.name);u.isDirectory()&&n.includes(u.name)||(u.isDirectory()?await i(c):qr(u.name)&&o.push(c))}}return await i(r),o}function qr(e){return!e.startsWith("_")&&(e.endsWith(".ts")||e.endsWith(".js"))}async function Ir(e,t,r=Math.max(1,Math.floor(Rt.cpus().length/2))){let o=Lr(e,r),n=[];for(let i of o){let a=(await Promise.allSettled(i.map(u=>t(u)))).filter(u=>u.status==="fulfilled").map(u=>u.value);n.push(...a)}return n}async function St(e){let t=await ee(e);return(await Ir(t,o=>_(o,e))).flat()}function Lr(e,t){let r=[];for(let o=0;o<e.length;o+=t)r.push(e.slice(o,o+t));return r}var F={fileChanges:0,totalReloadTime:0,averageReloadTime:0,slowReloads:[]};function xt(e,t){let r=Date.now()-t;if(F.fileChanges++,F.totalReloadTime+=r,F.averageReloadTime=F.totalReloadTime/F.fileChanges,r>100&&(F.slowReloads.push({file:e,time:r}),F.slowReloads.length>10&&F.slowReloads.shift()),process.env.NODE_ENV==="development"){let o=r<50?"\u26A1":r<100?"\u{1F504}":"\u{1F40C}";console.log(`${o} Route reload: ${e} (${r}ms)`)}}function le(e,t){return console.log(`Tracking performance for: ${t}`),async(...r)=>{let o=Date.now();try{let n=await e(...r);return xt(t,o),n}catch(n){throw xt(t,o),n}}}import*as ce from"node:path";import{watch as jr}from"chokidar";function de(e,t={}){let r=t.debounceMs||16,o=new Map;function n(d,g){return(...y)=>{let l=o.get(g);l&&clearTimeout(l);let p=setTimeout(()=>{d(...y),o.delete(g)},r);o.set(g,p)}}let i=new Map;async function s(){try{let d=await ee(e,{ignore:t.ignore});for(let g of d)await a(g)}catch(d){c(d)}}async function a(d){try{let g=i.get(d),y=await _(d,e,!1);if(!y||y.length===0||g&&!ht(d,y))return;await _(d,e,!0);let l=ce.normalize(d);g?(i.set(d,y),t.onRouteChanged&&t.onRouteChanged(l,y)):(i.set(d,y),t.onRouteAdded&&t.onRouteAdded(l,y))}catch(g){console.log(`\u26A0\uFE0F Error processing file ${d}:`,g),c(g)}}function u(d){let g=ce.normalize(d),y=i.get(g);y&&y.length>0&&t.onRouteRemoved&&t.onRouteRemoved(g,y),i.delete(g)}function c(d){t.onError&&d instanceof Error?t.onError(d):console.error("\u26A0\uFE0F Route watcher error:",d)}let w=jr(e,{awaitWriteFinish:{stabilityThreshold:50,pollInterval:10},usePolling:!1,atomic:!0,followSymlinks:!1,depth:10,ignored:[/(^|[/\\])\../,/node_modules/,/\.git/,/\.DS_Store/,/Thumbs\.db/,/\.(test|spec)\.(ts|js)$/,/\.d\.ts$/,/\.map$/,/~$/,...t.ignore||[]]});return w.on("add",d=>{n(a,d)(d)}).on("change",d=>{n(a,d)(d)}).on("unlink",d=>{n(u,d)(d)}).on("error",c),s().catch(c),{close:()=>(o.forEach(d=>clearTimeout(d)),o.clear(),w.close()),getRoutes:()=>{let d=[];for(let g of i.values())d.push(...g);return d},getRoutesByFile:()=>new Map(i)}}import{z as Gr}from"zod";import{z as _r}from"zod";function pe(e,t){return t instanceof _r.ZodObject?t.strict().parse(e):t.parse(e)}import{z as Qr}from"zod";function fe(e,t){return t instanceof Qr.ZodObject?t.strict().parse(e):t.parse(e)}import{z as Vr}from"zod";function me(e,t){return t instanceof Vr.ZodObject?t.strict().parse(e):t.parse(e)}import{z as Wr}from"zod";function ge(e,t){return t instanceof Wr.ZodObject?t.strict().parse(e):t.parse(e)}function ye(e,t=!1){return{name:"RequestValidator",execute:async(o,n)=>{if(e.params&&o.request.params)try{o.request.params=fe(o.request.params,e.params)}catch(i){let s=te(i),a=s.reduce((u,c)=>u+c.messages.length,0);throw new U("Request validation failed",{fields:s,errorCount:a,section:"params"})}if(e.query&&o.request.query)try{o.request.query=me(o.request.query,e.query)}catch(i){let s=te(i),a=s.reduce((u,c)=>u+c.messages.length,0);throw new U("Request validation failed",{fields:s,errorCount:a,section:"query"})}if(e.body)try{o.request.body=pe(o.request.body,e.body)}catch(i){let s=te(i),a=s.reduce((u,c)=>u+c.messages.length,0);throw new U("Request validation failed",{fields:s,errorCount:a,section:"body"})}await n()},debug:t}}function he(e,t=!1){return{name:"ResponseValidator",execute:async(o,n)=>{let i=o.response.json;o.response.json=(s,a)=>{try{let u=ge(s,e);return o.response.json=i,i.call(o.response,u,a)}catch(u){throw o.response.json=i,new H("Response validation failed",{responseSchema:e.description||"Unknown schema",validationError:te(u),originalResponse:s})}},await n()},debug:t}}function te(e){if(e instanceof Gr.ZodError){let t=new Map;for(let r of e.issues){let o=r.path.length>0?r.path.join("."):"root";t.has(o)||t.set(o,[]),t.get(o).push(r.message)}return Array.from(t.entries()).map(([r,o])=>({field:r,messages:o}))}return e instanceof Error?[{field:"unknown",messages:[e.message]}]:[{field:"unknown",messages:[String(e)]}]}async function we(e,t,r){let o=[...t.middleware||[]];t.schema&&((t.schema.params||t.schema.query||t.schema.body)&&o.unshift(ye(t.schema)),t.schema.response&&o.push(he(t.schema.response))),await D([...o])(e,async()=>{let i=await t.handler(e,r);!e.response.sent&&i!==void 0&&e.response.json(i)})}function re(e,t,r){let o=t.exec(e);if(!o)return{};let n={};for(let i=0;i<r.length;i++)n[r[i]]=o[i+1]||"";return n}function Re(e){let t=[];if(e==="/")return{pattern:/^\/$/,paramNames:[]};let r=e.replace(/([.+*?^$(){}|\\])/g,"\\$1");return r=r.replace(/\/:([^/]+)/g,(n,i)=>(t.push(i),"/([^/]+)")).replace(/\/\[([^\]]+)\]/g,(n,i)=>(t.push(i),"/([^/]+)")),r=`${r}(?:/)?`,{pattern:new RegExp(`^${r}$`),paramNames:t}}function Se(){let e=[];return{add(t,r,o){let{pattern:n,paramNames:i}=Re(t),s={path:t,method:r,pattern:n,paramNames:i,routeOptions:o},a=e.findIndex(u=>i.length<u.paramNames.length);a===-1?e.push(s):e.splice(a,0,s)},remove(t){for(let r=e.length-1;r>=0;r--)e[r].path===t&&e.splice(r,1)},clear(){e.length=0},match(t,r){let o=t.split("?")[0];if(!o)return null;for(let i of e){if(i.method!==r)continue;if(i.pattern.exec(o)){let a=re(t,i.pattern,i.paramNames);return{route:i.routeOptions,params:a}}}return e.find(i=>i.method!==r&&i.pattern.test(t))?{route:null,params:{},methodNotAllowed:!0,allowedMethods:e.filter(i=>i.pattern.test(t)).map(i=>i.method)}:null},getRoutes(){return e.map(t=>({path:t.path,method:t.method}))},findRoutes(t){return e.filter(r=>r.pattern.test(t)).map(r=>({path:r.path,method:r.method,params:re(t,r.pattern,r.paramNames)}))}}}function bt(){return{routesByPath:new Map,routesByFile:new Map,pathToFile:new Map}}function M(e,t,r){console.log(`Updating routes from file: ${t}`);let o=e.routesByFile.get(t)||new Set,n=new Set(r.map(c=>c.path)),i=r.filter(c=>!o.has(c.path)),s=Array.from(o).filter(c=>!n.has(c)),u=r.filter(c=>o.has(c.path)).filter(c=>{let w=e.routesByPath.get(c.path);return!w||!Jr(w,c)});return Zr(e,t,{added:i,removed:s,changed:u}),{added:i,removed:s,changed:u}}function Tt(e){return Array.from(e.routesByPath.values())}function Zr(e,t,r){let{added:o,removed:n,changed:i}=r;n.forEach(a=>{e.routesByPath.delete(a),e.pathToFile.delete(a)}),[...o,...i].forEach(a=>{e.routesByPath.set(a.path,a),e.pathToFile.set(a.path,t)});let s=new Set([...o.map(a=>a.path),...i.map(a=>a.path),...Array.from(e.routesByFile.get(t)||[]).filter(a=>!n.includes(a))]);s.size>0?e.routesByFile.set(t,s):e.routesByFile.delete(t)}function Jr(e,t){if(e.path!==t.path)return!1;let r=Object.keys(e).filter(n=>n!=="path").sort(),o=Object.keys(t).filter(n=>n!=="path").sort();return r.length!==o.length?!1:r.every(n=>{let i=e[n],s=t[n];return typeof i==typeof s})}function xe(e,t){Object.entries(e).forEach(([r,o])=>{r==="path"||!o||t.add(e.path,r,o)})}function Q(e,t){"remove"in t&&typeof t.remove=="function"?t.remove(e):console.warn("Matcher does not support selective removal, consider adding remove() method")}function Pt(e,t){Q(e.path,t),xe(e,t)}var Yr={routesDir:"./routes",basePath:"/",watchMode:process.env.NODE_ENV==="development"};function vt(e){let t={...Yr,...e};e.basePath&&!e.basePath.startsWith("/")&&console.warn("Base path does nothing");let r=bt(),o=Se(),n=!1,i=null,s=null,a=new Set([t.routesDir]);function u(l){console.log(`
|
|
28
|
+
\u{1F527} APPLYING MATCHER CHANGES:`),console.log(` Adding ${l.added.length} routes`),console.log(` Removing ${l.removed.length} routes`),console.log(` Updating ${l.changed.length} routes`),l.removed.forEach(p=>{console.log(` \u2796 Removing: ${p}`),Q(p,o)}),l.added.forEach(p=>{let m=Object.keys(p).filter(f=>f!=="path");console.log(` \u2795 Adding: ${p.path} [${m.join(", ")}]`),xe(p,o)}),l.changed.forEach(p=>{let m=Object.keys(p).filter(f=>f!=="path");console.log(` \u{1F504} Updating: ${p.path} [${m.join(", ")}]`),Pt(p,o)}),console.log(`\u2705 Matcher changes applied
|
|
29
|
+
`)}function c(l,p){try{let m=M(r,p,l);return u(m),m}catch(m){throw console.error(`\u26A0\uFE0F Route conflicts from ${p}:`,m),m}}async function w(l,p,m){try{let f=await St(l),h=f.map(v=>m?{...v,path:`${m}${v.path}`}:v),R=c(h,p);console.log(`Loaded ${f.length} routes from ${p}${m?` with prefix ${m}`:""} (${R.added.length} added, ${R.changed.length} changed, ${R.removed.length} removed)`)}catch(f){throw console.error(`\u26A0\uFE0F Failed to load routes from ${p}:`,f),f}}async function d(){return n||i||(i=(async()=>{try{await Promise.all(Array.from(a).map(l=>w(l,l))),t.watchMode&&g(),n=!0}catch(l){throw console.error("\u26A0\uFE0F Failed to initialize router:",l),l}})()),i}function g(){s||(s=new Map);for(let l of a)if(!s.has(l)){let p=de(l,{debounceMs:16,ignore:["node_modules",".git"],onRouteAdded:(m,f)=>{try{let h=M(r,m,f);u(h)}catch(h){console.error(`Error adding routes from ${l}:`,h)}},onRouteChanged:le(async(m,f)=>{try{console.log(`Processing changes for ${m}`);let h=M(r,m,f);console.log(`Changes detected: ${h.added.length} added, ${h.changed.length} changed, ${h.removed.length} removed`),u(h),console.log(`Route changes applied: ${h.added.length} added, ${h.changed.length} changed, ${h.removed.length} removed`)}catch(h){console.error(`\u26A0\uFE0F Error updating routes from ${l}:`,h)}},l),onRouteRemoved:(m,f)=>{console.log(`File removed: ${m} with ${f.length} routes`);try{f.forEach(h=>{Q(h.path,o)}),ue(m)}catch(h){console.error(`\u26A0\uFE0F Error removing routes from ${m}:`,h)}},onError:m=>{console.error(`\u26A0\uFE0F Route watcher error for ${l}:`,m)}});s.set(l,p)}}function y(l,p){s||(s=new Map);let m=de(l,{debounceMs:16,ignore:["node_modules",".git"],onRouteAdded:(f,h)=>{try{let R=h.map(z=>p?{...z,path:`${p}${z.path}`}:z),v=M(r,f,R);u(v)}catch(R){console.error(`\u26A0\uFE0F Error adding routes from ${l}:`,R)}},onRouteChanged:le(async(f,h)=>{try{let R=h.map(z=>p?{...z,path:`${p}${z.path}`}:z),v=M(r,f,R);u(v)}catch(R){console.error(`\u26A0\uFE0F Error updating routes from ${l}:`,R)}},l),onRouteRemoved:(f,h)=>{try{h.forEach(R=>{let v=p?`${p}${R.path}`:R.path;Q(v,o)}),ue(f)}catch(R){console.error(`Error removing routes from ${f}:`,R)}},onError:f=>{console.error(`\u26A0\uFE0F Route watcher error for ${l}:`,f)}});return s.set(l,m),m}return d().catch(l=>{console.error("\u26A0\uFE0F Failed to initialize router on creation:",l)}),{async handleRequest(l){n||(console.log("\u{1F504} Router not initialized, initializing..."),await d());let{method:p,path:m}=l.request;console.log(`
|
|
30
|
+
\u{1F4E5} Handling request: ${p} ${m}`);let f=o.match(m,p);if(!f)throw console.log(`\u274C No match found for: ${p} ${m}`),new k("Not found");if(console.log(`\u2705 Route matched: ${p} ${m}`),console.log(` Params: ${JSON.stringify(f.params)}`),f.methodNotAllowed){l.response.status(405).json({error:"\u274C Method Not Allowed",allowed:f.allowedMethods}),f.allowedMethods&&f.allowedMethods.length>0&&l.response.header("Allow",f.allowedMethods.join(", "));return}l.request.params=f.params,await we(l,f.route,f.params)},getRoutes(){return Tt(r)},addRoute(l){let p=M(r,"programmatic",[l]);u(p)},addRoutes(l){let p=M(r,"programmatic",l);return u(p),p},async addRouteDirectory(l,p={}){if(a.has(l)){console.warn(`Route directory ${l} already registered`);return}a.add(l),n&&(await w(l,l,p.prefix),t.watchMode&&y(l,p.prefix))},getRouteConflicts(){return[]},async close(){if(s){for(let l of s.values())await l.close();s.clear()}}}}var eo={port:3e3,host:"localhost",routesDir:"./routes",http2:{enabled:!0},middleware:[],plugins:[]};function to(e={}){let t={...eo};return ze({routesDir:e.routesDir||t.routesDir}),{port:e.port??t.port,host:e.host??t.host,routesDir:e.routesDir??t.routesDir,http2:{enabled:e.http2?.enabled??t.http2?.enabled,keyFile:e.http2?.keyFile??t.http2?.keyFile,certFile:e.http2?.certFile??t.http2?.certFile},middleware:[...t.middleware||[],...e.middleware||[]],plugins:[...t.plugins||[],...e.plugins||[]]}}function ro(e,t,r,o){return async()=>(await oo(e,r,o),await e.pluginManager.initializePlugins(e),await at(e,t),await e.pluginManager.onServerStart(e,e.server),no(e),e)}async function oo(e,t,r){for(let o of t)e.use(o);for(let o of r)await e.register(o)}function no(e){let t=lt(()=>e.close());e._signalHandlers=t,e.events.emit("started")}function io(e){return async t=>{if(!e.server)return;let r={...t};e._signalHandlers&&(e._signalHandlers.unregister(),delete e._signalHandlers),await ut(e,r)}}function so(e){return t=>{let r=Array.isArray(t)?t:[t];return e.middleware.push(...r),e}}function ao(e){return async t=>(pt(t),e.plugins.push(t),await t.register(e),e)}function be(e={}){let t=to(e),r;try{r=ct(t)}catch(l){throw l instanceof Error?new Error(`Failed to create server: ${l.message}`):new Error(`Failed to create server: ${String(l)}`)}let{port:o,host:n,middleware:i,plugins:s}=r,a=Array.isArray(i)?[...i]:[],u=Array.isArray(s)?[...s]:[],c=new Xr,w=vt({routesDir:r.routesDir,watchMode:process.env.NODE_ENV==="development"}),d=dt({debug:process.env.NODE_ENV==="development",continueOnError:!0}),g=new Kr,y={server:null,port:o,host:n,context:c,events:g,plugins:[],middleware:[],_signalHandlers:{unregister:()=>{}},use:()=>y,register:async()=>y,listen:async()=>y,close:async()=>{},router:w,pluginManager:d};return y.listen=ro(y,r,a,u),y.close=io(y),y.use=so(y),y.register=ao(y),y}var Te=class extends S{constructor(t,r=void 0,o=void 0){super("UNAUTHORIZED",t,401,o??x(),r)}};var Pe=class extends S{constructor(t,r=void 0,o=void 0){super("FORBIDDEN",t,403,o??x(),r)}};var ve=class extends S{constructor(t,r=void 0,o=void 0){super("CONFLICT",t,409,o??x(),r)}};var Ee=class extends S{constructor(t,r=void 0,o=void 0){super("RATE_LIMITED",t,429,o??x(),r)}};var Ce=class extends S{constructor(t,r,o){super("UPLOAD_TIMEOUT",t,408,o??x(),r)}};var Fe=class extends S{constructor(t,r,o){super("UNPROCESSABLE_ENTITY",t,422,o??x(),r)}};var uo="0.1.0",lo={createServer:be},co={createDeleteRoute:Ne,createGetRoute:Oe,createHeadRoute:He,createOptionsRoute:Ue,createPatchRoute:Ae,createPostRoute:ke,createPutRoute:De},po={createMiddleware:ne,compose:D},fo={createPlugin:ie},ys={createServer:be,createMiddleware:ne,createPlugin:ie,Server:lo,Router:co,Middleware:po,Plugins:fo,VERSION:uo};export{ys as Blaize,S as BlaizeError,ve as ConflictError,mo as ErrorSeverity,B as ErrorType,Pe as ForbiddenError,H as InternalServerError,po as MiddlewareAPI,k as NotFoundError,Et as PayloadTooLargeError,fo as PluginsAPI,Ee as RateLimitError,Ce as RequestTimeoutError,co as RouterAPI,lo as ServerAPI,Te as UnauthorizedError,Fe as UnprocessableEntityError,Ct as UnsupportedMediaTypeError,uo as VERSION,U as ValidationError,D as compose,Ne as createDeleteRoute,Oe as createGetRoute,He as createHeadRoute,ne as createMiddleware,Ue as createOptionsRoute,Ae as createPatchRoute,ie as createPlugin,ke as createPostRoute,De as createPutRoute,be as createServer,go as isBodyParseError};
|
|
3172
31
|
//# sourceMappingURL=index.js.map
|