blaizejs 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-QQCQRHXJ.js +38 -0
- package/dist/chunk-QQCQRHXJ.js.map +1 -0
- package/dist/chunk-SRD3AB6T.js +38 -0
- package/dist/chunk-SRD3AB6T.js.map +1 -0
- package/dist/chunk-TCPQMZ23.js +148 -0
- package/dist/chunk-TCPQMZ23.js.map +1 -0
- package/dist/index.cjs +1747 -412
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1189 -143
- package/dist/index.d.ts +1189 -143
- package/dist/index.js +1471 -409
- package/dist/index.js.map +1 -1
- package/dist/internal-server-error-CVRDTBLL.js +16 -0
- package/dist/internal-server-error-CVRDTBLL.js.map +1 -0
- package/dist/payload-too-large-error-PAYLDBZT.js +29 -0
- package/dist/payload-too-large-error-PAYLDBZT.js.map +1 -0
- package/dist/unsupported-media-type-error-MQZD7YQJ.js +29 -0
- package/dist/unsupported-media-type-error-MQZD7YQJ.js.map +1 -0
- package/dist/validation-error-CM6IKIJU.js +16 -0
- package/dist/validation-error-CM6IKIJU.js.map +1 -0
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* blaizejs v0.
|
|
2
|
+
* blaizejs v0.3.0
|
|
3
3
|
* A blazing-fast, TypeScript-first Node.js framework with HTTP/2 support, file-based routing, powerful middleware system, and end-to-end type safety for building modern APIs.
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) 2025 BlaizeJS Contributors
|
|
@@ -13,6 +13,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
13
13
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
14
|
var __getProtoOf = Object.getPrototypeOf;
|
|
15
15
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __esm = (fn, res) => function __init() {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
};
|
|
16
19
|
var __export = (target, all) => {
|
|
17
20
|
for (var name in all)
|
|
18
21
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -35,15 +38,276 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
38
|
));
|
|
36
39
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
40
|
|
|
41
|
+
// ../blaize-types/src/errors.ts
|
|
42
|
+
function isBodyParseError(error) {
|
|
43
|
+
return typeof error === "object" && error !== null && "type" in error && "message" in error && "error" in error && typeof error.type === "string" && typeof error.message === "string";
|
|
44
|
+
}
|
|
45
|
+
var ErrorType, ErrorSeverity, BlaizeError;
|
|
46
|
+
var init_errors = __esm({
|
|
47
|
+
"../blaize-types/src/errors.ts"() {
|
|
48
|
+
"use strict";
|
|
49
|
+
ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
50
|
+
ErrorType2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
51
|
+
ErrorType2["NOT_FOUND"] = "NOT_FOUND";
|
|
52
|
+
ErrorType2["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
53
|
+
ErrorType2["FORBIDDEN"] = "FORBIDDEN";
|
|
54
|
+
ErrorType2["CONFLICT"] = "CONFLICT";
|
|
55
|
+
ErrorType2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
56
|
+
ErrorType2["INTERNAL_SERVER_ERROR"] = "INTERNAL_SERVER_ERROR";
|
|
57
|
+
ErrorType2["PAYLOAD_TOO_LARGE"] = "PAYLOAD_TOO_LARGE";
|
|
58
|
+
ErrorType2["UNSUPPORTED_MEDIA_TYPE"] = "UNSUPPORTED_MEDIA_TYPE";
|
|
59
|
+
ErrorType2["UPLOAD_TIMEOUT"] = "UPLOAD_TIMEOUT";
|
|
60
|
+
ErrorType2["UNPROCESSABLE_ENTITY"] = "UNPROCESSABLE_ENTITY";
|
|
61
|
+
ErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
62
|
+
ErrorType2["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
63
|
+
ErrorType2["PARSE_ERROR"] = "PARSE_ERROR";
|
|
64
|
+
ErrorType2["HTTP_ERROR"] = "HTTP_ERROR";
|
|
65
|
+
return ErrorType2;
|
|
66
|
+
})(ErrorType || {});
|
|
67
|
+
ErrorSeverity = /* @__PURE__ */ ((ErrorSeverity2) => {
|
|
68
|
+
ErrorSeverity2["LOW"] = "low";
|
|
69
|
+
ErrorSeverity2["MEDIUM"] = "medium";
|
|
70
|
+
ErrorSeverity2["HIGH"] = "high";
|
|
71
|
+
ErrorSeverity2["CRITICAL"] = "critical";
|
|
72
|
+
return ErrorSeverity2;
|
|
73
|
+
})(ErrorSeverity || {});
|
|
74
|
+
BlaizeError = class extends Error {
|
|
75
|
+
/**
|
|
76
|
+
* Error type identifier from the ErrorType enum
|
|
77
|
+
* Used for programmatic error handling and client-side error routing
|
|
78
|
+
*/
|
|
79
|
+
type;
|
|
80
|
+
/**
|
|
81
|
+
* Human-readable error title/message
|
|
82
|
+
* Should be descriptive enough for debugging but safe for end users
|
|
83
|
+
*/
|
|
84
|
+
title;
|
|
85
|
+
/**
|
|
86
|
+
* HTTP status code associated with this error
|
|
87
|
+
* Used by the error boundary to set appropriate response status
|
|
88
|
+
*/
|
|
89
|
+
status;
|
|
90
|
+
/**
|
|
91
|
+
* Correlation ID for request tracing
|
|
92
|
+
* Links this error to the specific request that generated it
|
|
93
|
+
*/
|
|
94
|
+
correlationId;
|
|
95
|
+
/**
|
|
96
|
+
* Timestamp when the error occurred
|
|
97
|
+
* Useful for debugging and log correlation
|
|
98
|
+
*/
|
|
99
|
+
timestamp;
|
|
100
|
+
/**
|
|
101
|
+
* Additional error-specific details
|
|
102
|
+
* Type-safe error context that varies by error type
|
|
103
|
+
*/
|
|
104
|
+
details;
|
|
105
|
+
/**
|
|
106
|
+
* Creates a new BlaizeError instance
|
|
107
|
+
*
|
|
108
|
+
* @param type - Error type from the ErrorType enum
|
|
109
|
+
* @param title - Human-readable error message
|
|
110
|
+
* @param status - HTTP status code
|
|
111
|
+
* @param correlationId - Request correlation ID for tracing
|
|
112
|
+
* @param details - Optional error-specific details
|
|
113
|
+
*/
|
|
114
|
+
constructor(type, title, status, correlationId, details) {
|
|
115
|
+
super(title);
|
|
116
|
+
this.name = this.constructor.name;
|
|
117
|
+
this.type = type;
|
|
118
|
+
this.title = title;
|
|
119
|
+
this.status = status;
|
|
120
|
+
this.correlationId = correlationId;
|
|
121
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
122
|
+
this.details = details;
|
|
123
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
124
|
+
if (Error.captureStackTrace) {
|
|
125
|
+
Error.captureStackTrace(this, this.constructor);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Serializes the error to a plain object suitable for HTTP responses
|
|
130
|
+
*
|
|
131
|
+
* @returns Object representation of the error
|
|
132
|
+
*/
|
|
133
|
+
toJSON() {
|
|
134
|
+
const base = {
|
|
135
|
+
type: this.type,
|
|
136
|
+
title: this.title,
|
|
137
|
+
status: this.status,
|
|
138
|
+
correlationId: this.correlationId,
|
|
139
|
+
timestamp: this.timestamp.toISOString()
|
|
140
|
+
};
|
|
141
|
+
if (this.details !== void 0) {
|
|
142
|
+
return { ...base, details: this.details };
|
|
143
|
+
}
|
|
144
|
+
return base;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns a string representation of the error
|
|
148
|
+
* Includes correlation ID for easier debugging
|
|
149
|
+
*/
|
|
150
|
+
toString() {
|
|
151
|
+
return `${this.name}: ${this.title} [${this.correlationId}]`;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// src/errors/correlation.ts
|
|
158
|
+
function generateCorrelationId() {
|
|
159
|
+
const timestamp = Date.now().toString(36);
|
|
160
|
+
const random = Math.random().toString(36).substr(2, 9);
|
|
161
|
+
return `req_${timestamp}_${random}`;
|
|
162
|
+
}
|
|
163
|
+
function getCurrentCorrelationId() {
|
|
164
|
+
const stored = correlationStorage.getStore();
|
|
165
|
+
return stored && stored.trim() ? stored : "unknown";
|
|
166
|
+
}
|
|
167
|
+
var import_node_async_hooks2, correlationStorage;
|
|
168
|
+
var init_correlation = __esm({
|
|
169
|
+
"src/errors/correlation.ts"() {
|
|
170
|
+
"use strict";
|
|
171
|
+
import_node_async_hooks2 = require("async_hooks");
|
|
172
|
+
correlationStorage = new import_node_async_hooks2.AsyncLocalStorage();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// src/errors/internal-server-error.ts
|
|
177
|
+
var internal_server_error_exports = {};
|
|
178
|
+
__export(internal_server_error_exports, {
|
|
179
|
+
InternalServerError: () => InternalServerError
|
|
180
|
+
});
|
|
181
|
+
var InternalServerError;
|
|
182
|
+
var init_internal_server_error = __esm({
|
|
183
|
+
"src/errors/internal-server-error.ts"() {
|
|
184
|
+
"use strict";
|
|
185
|
+
init_errors();
|
|
186
|
+
init_correlation();
|
|
187
|
+
InternalServerError = class extends BlaizeError {
|
|
188
|
+
/**
|
|
189
|
+
* Creates a new InternalServerError instance
|
|
190
|
+
*
|
|
191
|
+
* @param title - Human-readable error message
|
|
192
|
+
* @param details - Optional debugging context
|
|
193
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
194
|
+
*/
|
|
195
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
196
|
+
super(
|
|
197
|
+
"INTERNAL_SERVER_ERROR" /* INTERNAL_SERVER_ERROR */,
|
|
198
|
+
title,
|
|
199
|
+
500,
|
|
200
|
+
// HTTP 500 Internal Server Error
|
|
201
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
202
|
+
details
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// src/errors/validation-error.ts
|
|
210
|
+
var validation_error_exports = {};
|
|
211
|
+
__export(validation_error_exports, {
|
|
212
|
+
ValidationError: () => ValidationError
|
|
213
|
+
});
|
|
214
|
+
var ValidationError;
|
|
215
|
+
var init_validation_error = __esm({
|
|
216
|
+
"src/errors/validation-error.ts"() {
|
|
217
|
+
"use strict";
|
|
218
|
+
init_errors();
|
|
219
|
+
init_correlation();
|
|
220
|
+
ValidationError = class extends BlaizeError {
|
|
221
|
+
/**
|
|
222
|
+
* Creates a new ValidationError instance
|
|
223
|
+
*
|
|
224
|
+
* @param title - Human-readable error message
|
|
225
|
+
* @param details - Optional structured validation details
|
|
226
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
227
|
+
*/
|
|
228
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
229
|
+
super(
|
|
230
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
231
|
+
title,
|
|
232
|
+
400,
|
|
233
|
+
// HTTP 400 Bad Request
|
|
234
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
235
|
+
details
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// src/errors/payload-too-large-error.ts
|
|
243
|
+
var payload_too_large_error_exports = {};
|
|
244
|
+
__export(payload_too_large_error_exports, {
|
|
245
|
+
PayloadTooLargeError: () => PayloadTooLargeError
|
|
246
|
+
});
|
|
247
|
+
var PayloadTooLargeError;
|
|
248
|
+
var init_payload_too_large_error = __esm({
|
|
249
|
+
"src/errors/payload-too-large-error.ts"() {
|
|
250
|
+
"use strict";
|
|
251
|
+
init_correlation();
|
|
252
|
+
init_errors();
|
|
253
|
+
PayloadTooLargeError = class extends BlaizeError {
|
|
254
|
+
constructor(title, details, correlationId) {
|
|
255
|
+
super(
|
|
256
|
+
"PAYLOAD_TOO_LARGE" /* PAYLOAD_TOO_LARGE */,
|
|
257
|
+
title,
|
|
258
|
+
413,
|
|
259
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
260
|
+
details
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/errors/unsupported-media-type-error.ts
|
|
268
|
+
var unsupported_media_type_error_exports = {};
|
|
269
|
+
__export(unsupported_media_type_error_exports, {
|
|
270
|
+
UnsupportedMediaTypeError: () => UnsupportedMediaTypeError
|
|
271
|
+
});
|
|
272
|
+
var UnsupportedMediaTypeError;
|
|
273
|
+
var init_unsupported_media_type_error = __esm({
|
|
274
|
+
"src/errors/unsupported-media-type-error.ts"() {
|
|
275
|
+
"use strict";
|
|
276
|
+
init_correlation();
|
|
277
|
+
init_errors();
|
|
278
|
+
UnsupportedMediaTypeError = class extends BlaizeError {
|
|
279
|
+
constructor(title, details, correlationId) {
|
|
280
|
+
super(
|
|
281
|
+
"UNSUPPORTED_MEDIA_TYPE" /* UNSUPPORTED_MEDIA_TYPE */,
|
|
282
|
+
title,
|
|
283
|
+
415,
|
|
284
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
285
|
+
details
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
38
292
|
// src/index.ts
|
|
39
293
|
var index_exports = {};
|
|
40
294
|
__export(index_exports, {
|
|
41
295
|
Blaize: () => Blaize,
|
|
296
|
+
BlaizeError: () => BlaizeError,
|
|
297
|
+
ConflictError: () => ConflictError,
|
|
298
|
+
ErrorSeverity: () => ErrorSeverity,
|
|
299
|
+
ErrorType: () => ErrorType,
|
|
300
|
+
ForbiddenError: () => ForbiddenError,
|
|
301
|
+
InternalServerError: () => InternalServerError,
|
|
42
302
|
MiddlewareAPI: () => MiddlewareAPI,
|
|
303
|
+
NotFoundError: () => NotFoundError,
|
|
43
304
|
PluginsAPI: () => PluginsAPI,
|
|
305
|
+
RateLimitError: () => RateLimitError,
|
|
44
306
|
RouterAPI: () => RouterAPI,
|
|
45
307
|
ServerAPI: () => ServerAPI,
|
|
308
|
+
UnauthorizedError: () => UnauthorizedError,
|
|
46
309
|
VERSION: () => VERSION,
|
|
310
|
+
ValidationError: () => ValidationError,
|
|
47
311
|
compose: () => compose,
|
|
48
312
|
createDeleteRoute: () => createDeleteRoute,
|
|
49
313
|
createGetRoute: () => createGetRoute,
|
|
@@ -55,7 +319,8 @@ __export(index_exports, {
|
|
|
55
319
|
createPostRoute: () => createPostRoute,
|
|
56
320
|
createPutRoute: () => createPutRoute,
|
|
57
321
|
createServer: () => create3,
|
|
58
|
-
default: () => index_default
|
|
322
|
+
default: () => index_default,
|
|
323
|
+
isBodyParseError: () => isBodyParseError
|
|
59
324
|
});
|
|
60
325
|
module.exports = __toCommonJS(index_exports);
|
|
61
326
|
|
|
@@ -159,6 +424,9 @@ function create2(name, version, setup, defaultOptions = {}) {
|
|
|
159
424
|
};
|
|
160
425
|
}
|
|
161
426
|
|
|
427
|
+
// src/router/create.ts
|
|
428
|
+
var import_node_url = require("url");
|
|
429
|
+
|
|
162
430
|
// src/config.ts
|
|
163
431
|
var config = {};
|
|
164
432
|
function setRuntimeConfig(newConfig) {
|
|
@@ -230,73 +498,75 @@ function getCallerFilePath() {
|
|
|
230
498
|
if (!fileName) {
|
|
231
499
|
throw new Error("Unable to determine caller file name");
|
|
232
500
|
}
|
|
501
|
+
if (fileName.startsWith("file://")) {
|
|
502
|
+
return (0, import_node_url.fileURLToPath)(fileName);
|
|
503
|
+
}
|
|
233
504
|
return fileName;
|
|
234
505
|
} finally {
|
|
235
506
|
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
236
507
|
}
|
|
237
508
|
}
|
|
238
509
|
function getRoutePath() {
|
|
239
|
-
console.log("getRoutePath called");
|
|
240
510
|
const callerPath = getCallerFilePath();
|
|
241
511
|
const routesDir = getRoutesDir();
|
|
242
512
|
const parsedRoute = parseRoutePath(callerPath, routesDir);
|
|
243
|
-
console.log(
|
|
513
|
+
console.log(`\u{1F50E} Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
|
|
244
514
|
return parsedRoute.routePath;
|
|
245
515
|
}
|
|
246
516
|
var createGetRoute = (config2) => {
|
|
247
517
|
validateMethodConfig("GET", config2);
|
|
248
|
-
const
|
|
518
|
+
const path6 = getRoutePath();
|
|
249
519
|
return {
|
|
250
520
|
GET: config2,
|
|
251
|
-
path:
|
|
521
|
+
path: path6
|
|
252
522
|
};
|
|
253
523
|
};
|
|
254
524
|
var createPostRoute = (config2) => {
|
|
255
525
|
validateMethodConfig("POST", config2);
|
|
256
|
-
const
|
|
526
|
+
const path6 = getRoutePath();
|
|
257
527
|
return {
|
|
258
528
|
POST: config2,
|
|
259
|
-
path:
|
|
529
|
+
path: path6
|
|
260
530
|
};
|
|
261
531
|
};
|
|
262
532
|
var createPutRoute = (config2) => {
|
|
263
533
|
validateMethodConfig("PUT", config2);
|
|
264
|
-
const
|
|
534
|
+
const path6 = getRoutePath();
|
|
265
535
|
return {
|
|
266
536
|
PUT: config2,
|
|
267
|
-
path:
|
|
537
|
+
path: path6
|
|
268
538
|
};
|
|
269
539
|
};
|
|
270
540
|
var createDeleteRoute = (config2) => {
|
|
271
541
|
validateMethodConfig("DELETE", config2);
|
|
272
|
-
const
|
|
542
|
+
const path6 = getRoutePath();
|
|
273
543
|
return {
|
|
274
544
|
DELETE: config2,
|
|
275
|
-
path:
|
|
545
|
+
path: path6
|
|
276
546
|
};
|
|
277
547
|
};
|
|
278
548
|
var createPatchRoute = (config2) => {
|
|
279
549
|
validateMethodConfig("PATCH", config2);
|
|
280
|
-
const
|
|
550
|
+
const path6 = getRoutePath();
|
|
281
551
|
return {
|
|
282
552
|
PATCH: config2,
|
|
283
|
-
path:
|
|
553
|
+
path: path6
|
|
284
554
|
};
|
|
285
555
|
};
|
|
286
556
|
var createHeadRoute = (config2) => {
|
|
287
557
|
validateMethodConfig("HEAD", config2);
|
|
288
|
-
const
|
|
558
|
+
const path6 = getRoutePath();
|
|
289
559
|
return {
|
|
290
560
|
HEAD: config2,
|
|
291
|
-
path:
|
|
561
|
+
path: path6
|
|
292
562
|
};
|
|
293
563
|
};
|
|
294
564
|
var createOptionsRoute = (config2) => {
|
|
295
565
|
validateMethodConfig("OPTIONS", config2);
|
|
296
|
-
const
|
|
566
|
+
const path6 = getRoutePath();
|
|
297
567
|
return {
|
|
298
568
|
OPTIONS: config2,
|
|
299
|
-
path:
|
|
569
|
+
path: path6
|
|
300
570
|
};
|
|
301
571
|
};
|
|
302
572
|
function validateMethodConfig(method, config2) {
|
|
@@ -336,7 +606,7 @@ function validateSchema(method, schema) {
|
|
|
336
606
|
}
|
|
337
607
|
|
|
338
608
|
// src/server/create.ts
|
|
339
|
-
var
|
|
609
|
+
var import_node_async_hooks3 = require("async_hooks");
|
|
340
610
|
var import_node_events = __toESM(require("events"), 1);
|
|
341
611
|
|
|
342
612
|
// src/server/start.ts
|
|
@@ -432,8 +702,474 @@ function runWithContext(context, callback) {
|
|
|
432
702
|
return contextStorage.run(context, callback);
|
|
433
703
|
}
|
|
434
704
|
|
|
705
|
+
// src/upload/multipart-parser.ts
|
|
706
|
+
var import_node_crypto = require("crypto");
|
|
707
|
+
var import_node_fs = require("fs");
|
|
708
|
+
var import_node_os = require("os");
|
|
709
|
+
var import_node_path = require("path");
|
|
710
|
+
var import_node_stream = require("stream");
|
|
711
|
+
|
|
712
|
+
// src/upload/utils.ts
|
|
713
|
+
var BOUNDARY_REGEX = /boundary=([^;]+)/i;
|
|
714
|
+
var CONTENT_DISPOSITION_REGEX = /Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;[\s\r\n]*filename="([^"]*)")?/i;
|
|
715
|
+
var CONTENT_TYPE_REGEX = /Content-Type:\s*([^\r\n]+)/i;
|
|
716
|
+
var MULTIPART_REGEX = /multipart\/form-data/i;
|
|
717
|
+
function extractBoundary(contentType) {
|
|
718
|
+
const match = contentType.match(BOUNDARY_REGEX);
|
|
719
|
+
if (!match || !match[1]) return null;
|
|
720
|
+
let boundary = match[1].trim();
|
|
721
|
+
if (boundary.startsWith('"') && boundary.endsWith('"')) {
|
|
722
|
+
boundary = boundary.slice(1, -1);
|
|
723
|
+
}
|
|
724
|
+
return boundary || null;
|
|
725
|
+
}
|
|
726
|
+
function parseContentDisposition(headers) {
|
|
727
|
+
const match = headers.match(CONTENT_DISPOSITION_REGEX);
|
|
728
|
+
if (!match || !match[1]) return null;
|
|
729
|
+
return {
|
|
730
|
+
name: match[1],
|
|
731
|
+
filename: match[2] !== void 0 ? match[2] : void 0
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function parseContentType(headers) {
|
|
735
|
+
const match = headers.match(CONTENT_TYPE_REGEX);
|
|
736
|
+
return match && match[1]?.trim() ? match[1].trim() : "application/octet-stream";
|
|
737
|
+
}
|
|
738
|
+
function isMultipartContent(contentType) {
|
|
739
|
+
return MULTIPART_REGEX.test(contentType);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/upload/multipart-parser.ts
|
|
743
|
+
var DEFAULT_OPTIONS = {
|
|
744
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
745
|
+
// 10MB
|
|
746
|
+
maxFiles: 10,
|
|
747
|
+
maxFieldSize: 1 * 1024 * 1024,
|
|
748
|
+
// 1MB
|
|
749
|
+
allowedMimeTypes: [],
|
|
750
|
+
allowedExtensions: [],
|
|
751
|
+
strategy: "stream",
|
|
752
|
+
tempDir: (0, import_node_os.tmpdir)(),
|
|
753
|
+
computeHash: false
|
|
754
|
+
};
|
|
755
|
+
function createParserState(boundary, options = {}) {
|
|
756
|
+
return {
|
|
757
|
+
boundary: Buffer.from(`--${boundary}`),
|
|
758
|
+
options: { ...DEFAULT_OPTIONS, ...options },
|
|
759
|
+
fields: /* @__PURE__ */ new Map(),
|
|
760
|
+
files: /* @__PURE__ */ new Map(),
|
|
761
|
+
buffer: Buffer.alloc(0),
|
|
762
|
+
stage: "boundary",
|
|
763
|
+
currentHeaders: "",
|
|
764
|
+
currentField: null,
|
|
765
|
+
currentFilename: void 0,
|
|
766
|
+
currentMimetype: "application/octet-stream",
|
|
767
|
+
currentContentLength: 0,
|
|
768
|
+
fileCount: 0,
|
|
769
|
+
fieldCount: 0,
|
|
770
|
+
currentBufferChunks: [],
|
|
771
|
+
currentStream: null,
|
|
772
|
+
currentTempPath: null,
|
|
773
|
+
currentWriteStream: null,
|
|
774
|
+
streamController: null,
|
|
775
|
+
cleanupTasks: [],
|
|
776
|
+
// Track validation state
|
|
777
|
+
hasFoundValidBoundary: false,
|
|
778
|
+
hasProcessedAnyPart: false,
|
|
779
|
+
isFinished: false
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
async function processChunk(state, chunk) {
|
|
783
|
+
const newBuffer = Buffer.concat([state.buffer, chunk]);
|
|
784
|
+
let currentState = { ...state, buffer: newBuffer };
|
|
785
|
+
while (currentState.buffer.length > 0 && !currentState.isFinished) {
|
|
786
|
+
const nextState = await processCurrentStage(currentState);
|
|
787
|
+
if (nextState === currentState) break;
|
|
788
|
+
currentState = nextState;
|
|
789
|
+
}
|
|
790
|
+
return currentState;
|
|
791
|
+
}
|
|
792
|
+
async function processCurrentStage(state) {
|
|
793
|
+
switch (state.stage) {
|
|
794
|
+
case "boundary":
|
|
795
|
+
return processBoundary(state);
|
|
796
|
+
case "headers":
|
|
797
|
+
return processHeaders(state);
|
|
798
|
+
case "content":
|
|
799
|
+
return processContent(state);
|
|
800
|
+
default: {
|
|
801
|
+
const { InternalServerError: InternalServerError2 } = await Promise.resolve().then(() => (init_internal_server_error(), internal_server_error_exports));
|
|
802
|
+
throw new InternalServerError2(`Invalid parser stage`, {
|
|
803
|
+
operation: state.stage
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function processBoundary(state) {
|
|
809
|
+
const boundaryIndex = state.buffer.indexOf(state.boundary);
|
|
810
|
+
if (boundaryIndex === -1) return state;
|
|
811
|
+
const hasFoundValidBoundary = true;
|
|
812
|
+
let buffer = state.buffer.subarray(boundaryIndex + state.boundary.length);
|
|
813
|
+
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("--"))) {
|
|
814
|
+
return {
|
|
815
|
+
...state,
|
|
816
|
+
buffer,
|
|
817
|
+
hasFoundValidBoundary,
|
|
818
|
+
isFinished: true,
|
|
819
|
+
stage: "boundary"
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("\r\n"))) {
|
|
823
|
+
buffer = buffer.subarray(2);
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
...state,
|
|
827
|
+
buffer,
|
|
828
|
+
hasFoundValidBoundary,
|
|
829
|
+
stage: "headers",
|
|
830
|
+
currentHeaders: ""
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
async function processHeaders(state) {
|
|
834
|
+
const headerEnd = state.buffer.indexOf("\r\n\r\n");
|
|
835
|
+
if (headerEnd === -1) return state;
|
|
836
|
+
const headers = state.buffer.subarray(0, headerEnd).toString("utf8");
|
|
837
|
+
const buffer = state.buffer.subarray(headerEnd + 4);
|
|
838
|
+
const disposition = parseContentDisposition(headers);
|
|
839
|
+
if (!disposition) {
|
|
840
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
841
|
+
throw new ValidationError2("Missing or invalid Content-Disposition header");
|
|
842
|
+
}
|
|
843
|
+
const mimetype = parseContentType(headers);
|
|
844
|
+
const isFile = disposition.filename !== void 0;
|
|
845
|
+
if (isFile && state.fileCount >= state.options.maxFiles) {
|
|
846
|
+
const { PayloadTooLargeError: PayloadTooLargeError2 } = await Promise.resolve().then(() => (init_payload_too_large_error(), payload_too_large_error_exports));
|
|
847
|
+
throw new PayloadTooLargeError2("Too many files in upload", {
|
|
848
|
+
fileCount: state.fileCount + 1,
|
|
849
|
+
maxFiles: state.options.maxFiles,
|
|
850
|
+
filename: disposition.filename
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
if (isFile && state.options.allowedMimeTypes.length > 0 && !state.options.allowedMimeTypes.includes(mimetype)) {
|
|
854
|
+
const { UnsupportedMediaTypeError: UnsupportedMediaTypeError2 } = await Promise.resolve().then(() => (init_unsupported_media_type_error(), unsupported_media_type_error_exports));
|
|
855
|
+
throw new UnsupportedMediaTypeError2("File type not allowed", {
|
|
856
|
+
receivedMimeType: mimetype,
|
|
857
|
+
allowedMimeTypes: state.options.allowedMimeTypes,
|
|
858
|
+
filename: disposition.filename
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
...state,
|
|
863
|
+
buffer,
|
|
864
|
+
stage: "content",
|
|
865
|
+
currentHeaders: headers,
|
|
866
|
+
currentField: disposition.name,
|
|
867
|
+
currentFilename: disposition.filename,
|
|
868
|
+
currentMimetype: mimetype,
|
|
869
|
+
currentContentLength: 0,
|
|
870
|
+
fileCount: isFile ? state.fileCount + 1 : state.fileCount,
|
|
871
|
+
fieldCount: isFile ? state.fieldCount : state.fieldCount + 1,
|
|
872
|
+
currentBufferChunks: []
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
async function processContent(state) {
|
|
876
|
+
const nextBoundaryIndex = state.buffer.indexOf(state.boundary);
|
|
877
|
+
let contentChunk;
|
|
878
|
+
let isComplete = false;
|
|
879
|
+
let buffer = state.buffer;
|
|
880
|
+
if (nextBoundaryIndex === -1) {
|
|
881
|
+
const safeLength = Math.max(0, state.buffer.length - state.boundary.length);
|
|
882
|
+
if (safeLength === 0) return state;
|
|
883
|
+
contentChunk = state.buffer.subarray(0, safeLength);
|
|
884
|
+
buffer = state.buffer.subarray(safeLength);
|
|
885
|
+
} else {
|
|
886
|
+
const contentEnd = Math.max(0, nextBoundaryIndex - 2);
|
|
887
|
+
contentChunk = state.buffer.subarray(0, contentEnd);
|
|
888
|
+
buffer = state.buffer.subarray(nextBoundaryIndex);
|
|
889
|
+
isComplete = true;
|
|
890
|
+
}
|
|
891
|
+
let updatedState = { ...state, buffer };
|
|
892
|
+
if (contentChunk.length > 0) {
|
|
893
|
+
updatedState = await processContentChunk(updatedState, contentChunk);
|
|
894
|
+
}
|
|
895
|
+
if (isComplete) {
|
|
896
|
+
updatedState = await finalizeCurrentPart(updatedState);
|
|
897
|
+
updatedState = {
|
|
898
|
+
...updatedState,
|
|
899
|
+
stage: "boundary",
|
|
900
|
+
hasProcessedAnyPart: true
|
|
901
|
+
// Mark that we've processed at least one part
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
return updatedState;
|
|
905
|
+
}
|
|
906
|
+
async function processContentChunk(state, chunk) {
|
|
907
|
+
const newContentLength = state.currentContentLength + chunk.length;
|
|
908
|
+
const maxSize = state.currentFilename !== void 0 ? state.options.maxFileSize : state.options.maxFieldSize;
|
|
909
|
+
if (newContentLength > maxSize) {
|
|
910
|
+
const isFile = state.currentFilename !== void 0;
|
|
911
|
+
const { PayloadTooLargeError: PayloadTooLargeError2 } = await Promise.resolve().then(() => (init_payload_too_large_error(), payload_too_large_error_exports));
|
|
912
|
+
const payloadErrorDetals = state.currentField ? {
|
|
913
|
+
contentType: isFile ? "file" : "field",
|
|
914
|
+
currentSize: newContentLength,
|
|
915
|
+
maxSize,
|
|
916
|
+
field: state.currentField,
|
|
917
|
+
filename: state.currentFilename
|
|
918
|
+
} : {
|
|
919
|
+
contentType: isFile ? "file" : "field",
|
|
920
|
+
currentSize: newContentLength,
|
|
921
|
+
maxSize,
|
|
922
|
+
filename: state.currentFilename
|
|
923
|
+
};
|
|
924
|
+
throw new PayloadTooLargeError2(
|
|
925
|
+
`${isFile ? "File" : "Field"} size exceeds limit`,
|
|
926
|
+
payloadErrorDetals
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
if (state.currentFilename !== void 0) {
|
|
930
|
+
return processFileChunk(state, chunk, newContentLength);
|
|
931
|
+
} else {
|
|
932
|
+
return {
|
|
933
|
+
...state,
|
|
934
|
+
currentContentLength: newContentLength,
|
|
935
|
+
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async function processFileChunk(state, chunk, newContentLength) {
|
|
940
|
+
switch (state.options.strategy) {
|
|
941
|
+
case "memory":
|
|
942
|
+
return {
|
|
943
|
+
...state,
|
|
944
|
+
currentContentLength: newContentLength,
|
|
945
|
+
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
946
|
+
};
|
|
947
|
+
case "stream":
|
|
948
|
+
if (state.streamController) {
|
|
949
|
+
state.streamController.enqueue(chunk);
|
|
950
|
+
}
|
|
951
|
+
return { ...state, currentContentLength: newContentLength };
|
|
952
|
+
case "temp":
|
|
953
|
+
if (state.currentWriteStream) {
|
|
954
|
+
await writeToStream(state.currentWriteStream, chunk);
|
|
955
|
+
}
|
|
956
|
+
return { ...state, currentContentLength: newContentLength };
|
|
957
|
+
default: {
|
|
958
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
959
|
+
throw new ValidationError2(`Invalid parsing strategy`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async function initializeFileProcessing(state) {
|
|
964
|
+
if (state.currentFilename === void 0) return state;
|
|
965
|
+
switch (state.options.strategy) {
|
|
966
|
+
case "memory":
|
|
967
|
+
return { ...state, currentBufferChunks: [] };
|
|
968
|
+
case "stream": {
|
|
969
|
+
let streamController = null;
|
|
970
|
+
const stream = new ReadableStream({
|
|
971
|
+
start: (controller) => {
|
|
972
|
+
streamController = controller;
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
...state,
|
|
977
|
+
currentStream: stream,
|
|
978
|
+
// Type cast for Node.js compatibility
|
|
979
|
+
streamController
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
case "temp": {
|
|
983
|
+
const tempPath = (0, import_node_path.join)(state.options.tempDir, `upload-${(0, import_node_crypto.randomUUID)()}`);
|
|
984
|
+
const writeStream = (0, import_node_fs.createWriteStream)(tempPath);
|
|
985
|
+
const cleanupTask = async () => {
|
|
986
|
+
try {
|
|
987
|
+
const { unlink } = await import("fs/promises");
|
|
988
|
+
await unlink(tempPath);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
console.warn(`Failed to cleanup temp file: ${tempPath}`, error);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
return {
|
|
994
|
+
...state,
|
|
995
|
+
currentTempPath: tempPath,
|
|
996
|
+
currentWriteStream: writeStream,
|
|
997
|
+
cleanupTasks: [...state.cleanupTasks, cleanupTask]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
default: {
|
|
1001
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
1002
|
+
throw new ValidationError2(`Invalid file processing strategy`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
async function finalizeCurrentPart(state) {
|
|
1007
|
+
if (!state.currentField) return resetCurrentPart(state);
|
|
1008
|
+
if (state.currentFilename !== void 0) {
|
|
1009
|
+
return finalizeFile(state);
|
|
1010
|
+
} else {
|
|
1011
|
+
return finalizeField(state);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function finalizeFile(state) {
|
|
1015
|
+
if (!state.currentField || state.currentFilename === void 0) {
|
|
1016
|
+
return resetCurrentPart(state);
|
|
1017
|
+
}
|
|
1018
|
+
let stream;
|
|
1019
|
+
let buffer;
|
|
1020
|
+
let tempPath;
|
|
1021
|
+
switch (state.options.strategy) {
|
|
1022
|
+
case "memory":
|
|
1023
|
+
buffer = Buffer.concat(state.currentBufferChunks);
|
|
1024
|
+
stream = import_node_stream.Readable.from(buffer);
|
|
1025
|
+
break;
|
|
1026
|
+
case "stream":
|
|
1027
|
+
if (state.streamController) {
|
|
1028
|
+
state.streamController.close();
|
|
1029
|
+
}
|
|
1030
|
+
stream = state.currentStream;
|
|
1031
|
+
break;
|
|
1032
|
+
case "temp":
|
|
1033
|
+
if (state.currentWriteStream) {
|
|
1034
|
+
await closeStream(state.currentWriteStream);
|
|
1035
|
+
}
|
|
1036
|
+
tempPath = state.currentTempPath;
|
|
1037
|
+
stream = import_node_stream.Readable.from(Buffer.alloc(0));
|
|
1038
|
+
break;
|
|
1039
|
+
default: {
|
|
1040
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
1041
|
+
throw new ValidationError2(`Invalid file finalization strategy`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const file = {
|
|
1045
|
+
filename: state.currentFilename,
|
|
1046
|
+
fieldname: state.currentField,
|
|
1047
|
+
mimetype: state.currentMimetype,
|
|
1048
|
+
size: state.currentContentLength,
|
|
1049
|
+
stream,
|
|
1050
|
+
buffer,
|
|
1051
|
+
tempPath
|
|
1052
|
+
};
|
|
1053
|
+
const updatedFiles = addToCollection(state.files, state.currentField, file);
|
|
1054
|
+
return {
|
|
1055
|
+
...resetCurrentPart(state),
|
|
1056
|
+
files: updatedFiles
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function finalizeField(state) {
|
|
1060
|
+
if (!state.currentField) return resetCurrentPart(state);
|
|
1061
|
+
const value = Buffer.concat(state.currentBufferChunks).toString("utf8");
|
|
1062
|
+
const updatedFields = addToCollection(state.fields, state.currentField, value);
|
|
1063
|
+
return {
|
|
1064
|
+
...resetCurrentPart(state),
|
|
1065
|
+
fields: updatedFields
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function resetCurrentPart(state) {
|
|
1069
|
+
return {
|
|
1070
|
+
...state,
|
|
1071
|
+
currentField: null,
|
|
1072
|
+
currentFilename: void 0,
|
|
1073
|
+
currentContentLength: 0,
|
|
1074
|
+
currentBufferChunks: [],
|
|
1075
|
+
currentStream: null,
|
|
1076
|
+
streamController: null,
|
|
1077
|
+
currentTempPath: null,
|
|
1078
|
+
currentWriteStream: null
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function addToCollection(collection, key, value) {
|
|
1082
|
+
const newCollection = new Map(collection);
|
|
1083
|
+
const existing = newCollection.get(key) || [];
|
|
1084
|
+
newCollection.set(key, [...existing, value]);
|
|
1085
|
+
return newCollection;
|
|
1086
|
+
}
|
|
1087
|
+
async function finalize(state) {
|
|
1088
|
+
if (!state.hasFoundValidBoundary) {
|
|
1089
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
1090
|
+
throw new ValidationError2("No valid multipart boundary found");
|
|
1091
|
+
}
|
|
1092
|
+
if (state.hasFoundValidBoundary && !state.hasProcessedAnyPart) {
|
|
1093
|
+
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_validation_error(), validation_error_exports));
|
|
1094
|
+
throw new ValidationError2("Empty multipart request");
|
|
1095
|
+
}
|
|
1096
|
+
const fields = {};
|
|
1097
|
+
for (const [key, values] of state.fields.entries()) {
|
|
1098
|
+
fields[key] = values.length === 1 ? values[0] : values;
|
|
1099
|
+
}
|
|
1100
|
+
const files = {};
|
|
1101
|
+
for (const [key, fileList] of state.files.entries()) {
|
|
1102
|
+
files[key] = fileList.length === 1 ? fileList[0] : fileList;
|
|
1103
|
+
}
|
|
1104
|
+
return { fields, files };
|
|
1105
|
+
}
|
|
1106
|
+
async function cleanup(state) {
|
|
1107
|
+
await Promise.allSettled(state.cleanupTasks.map((task) => task()));
|
|
1108
|
+
if (state.streamController) {
|
|
1109
|
+
state.streamController.close();
|
|
1110
|
+
}
|
|
1111
|
+
if (state.currentWriteStream) {
|
|
1112
|
+
await closeStream(state.currentWriteStream);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function writeToStream(stream, chunk) {
|
|
1116
|
+
return new Promise((resolve3, reject) => {
|
|
1117
|
+
stream.write(chunk, (error) => {
|
|
1118
|
+
if (error) reject(error);
|
|
1119
|
+
else resolve3();
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
async function closeStream(stream) {
|
|
1124
|
+
return new Promise((resolve3) => {
|
|
1125
|
+
stream.end(() => resolve3());
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
async function parseMultipartRequest(request, options = {}) {
|
|
1129
|
+
const contentType = request.headers["content-type"] || "";
|
|
1130
|
+
const boundary = extractBoundary(contentType);
|
|
1131
|
+
if (!boundary) {
|
|
1132
|
+
const { UnsupportedMediaTypeError: UnsupportedMediaTypeError2 } = await Promise.resolve().then(() => (init_unsupported_media_type_error(), unsupported_media_type_error_exports));
|
|
1133
|
+
throw new UnsupportedMediaTypeError2("Missing boundary in multipart content-type", {
|
|
1134
|
+
receivedContentType: contentType,
|
|
1135
|
+
expectedFormat: "multipart/form-data; boundary=..."
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
let state = createParserState(boundary, options);
|
|
1139
|
+
if (state.currentFilename !== void 0) {
|
|
1140
|
+
state = await initializeFileProcessing(state);
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
for await (const chunk of request) {
|
|
1144
|
+
state = await processChunk(state, chunk);
|
|
1145
|
+
}
|
|
1146
|
+
return finalize(state);
|
|
1147
|
+
} finally {
|
|
1148
|
+
await cleanup(state);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
435
1152
|
// src/context/create.ts
|
|
436
1153
|
var CONTENT_TYPE_HEADER = "Content-Type";
|
|
1154
|
+
var DEFAULT_BODY_LIMITS = {
|
|
1155
|
+
json: 512 * 1024,
|
|
1156
|
+
// 512KB - Most APIs should be much smaller
|
|
1157
|
+
form: 1024 * 1024,
|
|
1158
|
+
// 1MB - Reasonable for form submissions
|
|
1159
|
+
text: 5 * 1024 * 1024,
|
|
1160
|
+
// 5MB - Documents, logs, code files
|
|
1161
|
+
multipart: {
|
|
1162
|
+
maxFileSize: 50 * 1024 * 1024,
|
|
1163
|
+
// 50MB per file
|
|
1164
|
+
maxTotalSize: 100 * 1024 * 1024,
|
|
1165
|
+
// 100MB total request
|
|
1166
|
+
maxFiles: 10,
|
|
1167
|
+
maxFieldSize: 1024 * 1024
|
|
1168
|
+
// 1MB for form fields
|
|
1169
|
+
},
|
|
1170
|
+
raw: 10 * 1024 * 1024
|
|
1171
|
+
// 10MB for unknown content types
|
|
1172
|
+
};
|
|
437
1173
|
function parseRequestUrl(req) {
|
|
438
1174
|
const originalUrl = req.url || "/";
|
|
439
1175
|
const host = req.headers.host || "localhost";
|
|
@@ -441,7 +1177,7 @@ function parseRequestUrl(req) {
|
|
|
441
1177
|
const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
|
|
442
1178
|
try {
|
|
443
1179
|
const url = new URL(fullUrl);
|
|
444
|
-
const
|
|
1180
|
+
const path6 = url.pathname;
|
|
445
1181
|
const query = {};
|
|
446
1182
|
url.searchParams.forEach((value, key) => {
|
|
447
1183
|
if (query[key] !== void 0) {
|
|
@@ -454,7 +1190,7 @@ function parseRequestUrl(req) {
|
|
|
454
1190
|
query[key] = value;
|
|
455
1191
|
}
|
|
456
1192
|
});
|
|
457
|
-
return { path:
|
|
1193
|
+
return { path: path6, url, query };
|
|
458
1194
|
} catch (error) {
|
|
459
1195
|
console.warn(`Invalid URL: ${fullUrl}`, error);
|
|
460
1196
|
throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
|
|
@@ -476,7 +1212,7 @@ function getProtocol(req) {
|
|
|
476
1212
|
return encrypted ? "https" : "http";
|
|
477
1213
|
}
|
|
478
1214
|
async function createContext(req, res, options = {}) {
|
|
479
|
-
const { path:
|
|
1215
|
+
const { path: path6, url, query } = parseRequestUrl(req);
|
|
480
1216
|
const method = req.method || "GET";
|
|
481
1217
|
const isHttp2 = isHttp2Request(req);
|
|
482
1218
|
const protocol = getProtocol(req);
|
|
@@ -485,7 +1221,7 @@ async function createContext(req, res, options = {}) {
|
|
|
485
1221
|
const responseState = { sent: false };
|
|
486
1222
|
const ctx = {
|
|
487
1223
|
request: createRequestObject(req, {
|
|
488
|
-
path:
|
|
1224
|
+
path: path6,
|
|
489
1225
|
url,
|
|
490
1226
|
query,
|
|
491
1227
|
params,
|
|
@@ -498,7 +1234,7 @@ async function createContext(req, res, options = {}) {
|
|
|
498
1234
|
};
|
|
499
1235
|
ctx.response = createResponseObject(res, responseState, ctx);
|
|
500
1236
|
if (options.parseBody) {
|
|
501
|
-
await parseBodyIfNeeded(req, ctx);
|
|
1237
|
+
await parseBodyIfNeeded(req, ctx, options);
|
|
502
1238
|
}
|
|
503
1239
|
return ctx;
|
|
504
1240
|
}
|
|
@@ -674,34 +1410,60 @@ function createStreamResponder(res, responseState) {
|
|
|
674
1410
|
});
|
|
675
1411
|
};
|
|
676
1412
|
}
|
|
677
|
-
async function parseBodyIfNeeded(req, ctx) {
|
|
1413
|
+
async function parseBodyIfNeeded(req, ctx, options = {}) {
|
|
678
1414
|
if (shouldSkipParsing(req.method)) {
|
|
679
1415
|
return;
|
|
680
1416
|
}
|
|
681
1417
|
const contentType = req.headers["content-type"] || "";
|
|
682
1418
|
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
|
683
|
-
if (contentLength === 0
|
|
1419
|
+
if (contentLength === 0) {
|
|
684
1420
|
return;
|
|
685
1421
|
}
|
|
1422
|
+
const limits = {
|
|
1423
|
+
json: options.bodyLimits?.json ?? DEFAULT_BODY_LIMITS.json,
|
|
1424
|
+
form: options.bodyLimits?.form ?? DEFAULT_BODY_LIMITS.form,
|
|
1425
|
+
text: options.bodyLimits?.text ?? DEFAULT_BODY_LIMITS.text,
|
|
1426
|
+
raw: options.bodyLimits?.raw ?? DEFAULT_BODY_LIMITS.raw,
|
|
1427
|
+
multipart: {
|
|
1428
|
+
maxFileSize: options.bodyLimits?.multipart?.maxFileSize ?? DEFAULT_BODY_LIMITS.multipart.maxFileSize,
|
|
1429
|
+
maxFiles: options.bodyLimits?.multipart?.maxFiles ?? DEFAULT_BODY_LIMITS.multipart.maxFiles,
|
|
1430
|
+
maxFieldSize: options.bodyLimits?.multipart?.maxFieldSize ?? DEFAULT_BODY_LIMITS.multipart.maxFieldSize,
|
|
1431
|
+
maxTotalSize: options.bodyLimits?.multipart?.maxTotalSize ?? DEFAULT_BODY_LIMITS.multipart.maxTotalSize
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
686
1434
|
try {
|
|
687
|
-
|
|
1435
|
+
if (contentType.includes("application/json")) {
|
|
1436
|
+
if (contentLength > limits.json) {
|
|
1437
|
+
throw new Error(`JSON body too large: ${contentLength} > ${limits.json} bytes`);
|
|
1438
|
+
}
|
|
1439
|
+
await parseJsonBody(req, ctx);
|
|
1440
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1441
|
+
if (contentLength > limits.form) {
|
|
1442
|
+
throw new Error(`Form body too large: ${contentLength} > ${limits.form} bytes`);
|
|
1443
|
+
}
|
|
1444
|
+
await parseFormUrlEncodedBody(req, ctx);
|
|
1445
|
+
} else if (contentType.includes("text/")) {
|
|
1446
|
+
if (contentLength > limits.text) {
|
|
1447
|
+
throw new Error(`Text body too large: ${contentLength} > ${limits.text} bytes`);
|
|
1448
|
+
}
|
|
1449
|
+
await parseTextBody(req, ctx);
|
|
1450
|
+
} else if (isMultipartContent(contentType)) {
|
|
1451
|
+
await parseMultipartBody(req, ctx, limits.multipart);
|
|
1452
|
+
} else {
|
|
1453
|
+
if (contentLength > limits.raw) {
|
|
1454
|
+
throw new Error(`Request body too large: ${contentLength} > ${limits.raw} bytes`);
|
|
1455
|
+
}
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
688
1458
|
} catch (error) {
|
|
689
|
-
|
|
1459
|
+
const errorType = contentType.includes("multipart") ? "multipart_parse_error" : "body_read_error";
|
|
1460
|
+
setBodyError(ctx, errorType, "Error reading request body", error);
|
|
690
1461
|
}
|
|
691
1462
|
}
|
|
692
1463
|
function shouldSkipParsing(method) {
|
|
693
1464
|
const skipMethods = ["GET", "HEAD", "OPTIONS"];
|
|
694
1465
|
return skipMethods.includes(method || "GET");
|
|
695
1466
|
}
|
|
696
|
-
async function parseBodyByContentType(req, ctx, contentType) {
|
|
697
|
-
if (contentType.includes("application/json")) {
|
|
698
|
-
await parseJsonBody(req, ctx);
|
|
699
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
700
|
-
await parseFormUrlEncodedBody(req, ctx);
|
|
701
|
-
} else if (contentType.includes("text/")) {
|
|
702
|
-
await parseTextBody(req, ctx);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
1467
|
async function parseJsonBody(req, ctx) {
|
|
706
1468
|
const body = await readRequestBody(req);
|
|
707
1469
|
if (!body) {
|
|
@@ -753,17 +1515,36 @@ async function parseTextBody(req, ctx) {
|
|
|
753
1515
|
ctx.request.body = body;
|
|
754
1516
|
}
|
|
755
1517
|
}
|
|
1518
|
+
async function parseMultipartBody(req, ctx, multipartLimits) {
|
|
1519
|
+
try {
|
|
1520
|
+
const limits = multipartLimits || DEFAULT_BODY_LIMITS.multipart;
|
|
1521
|
+
const multipartData = await parseMultipartRequest(req, {
|
|
1522
|
+
strategy: "stream",
|
|
1523
|
+
maxFileSize: limits.maxFileSize,
|
|
1524
|
+
maxFiles: limits.maxFiles,
|
|
1525
|
+
maxFieldSize: limits.maxFieldSize
|
|
1526
|
+
// Could add total size validation here
|
|
1527
|
+
});
|
|
1528
|
+
ctx.request.multipart = multipartData;
|
|
1529
|
+
ctx.request.files = multipartData.files;
|
|
1530
|
+
ctx.request.body = multipartData.fields;
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
ctx.request.body = null;
|
|
1533
|
+
setBodyError(ctx, "multipart_parse_error", "Failed to parse multipart data", error);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
756
1536
|
function setBodyError(ctx, type, message, error) {
|
|
757
|
-
|
|
1537
|
+
const bodyError = { type, message, error };
|
|
1538
|
+
ctx.state._bodyError = bodyError;
|
|
758
1539
|
}
|
|
759
1540
|
async function readRequestBody(req) {
|
|
760
|
-
return new Promise((
|
|
1541
|
+
return new Promise((resolve3, reject) => {
|
|
761
1542
|
const chunks = [];
|
|
762
1543
|
req.on("data", (chunk) => {
|
|
763
1544
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
764
1545
|
});
|
|
765
1546
|
req.on("end", () => {
|
|
766
|
-
|
|
1547
|
+
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
767
1548
|
});
|
|
768
1549
|
req.on("error", (err) => {
|
|
769
1550
|
reject(err);
|
|
@@ -771,6 +1552,107 @@ async function readRequestBody(req) {
|
|
|
771
1552
|
});
|
|
772
1553
|
}
|
|
773
1554
|
|
|
1555
|
+
// src/errors/not-found-error.ts
|
|
1556
|
+
init_errors();
|
|
1557
|
+
init_correlation();
|
|
1558
|
+
var NotFoundError = class extends BlaizeError {
|
|
1559
|
+
/**
|
|
1560
|
+
* Creates a new NotFoundError instance
|
|
1561
|
+
*
|
|
1562
|
+
* @param title - Human-readable error message
|
|
1563
|
+
* @param details - Optional context about the missing resource
|
|
1564
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
1565
|
+
*/
|
|
1566
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
1567
|
+
super(
|
|
1568
|
+
"NOT_FOUND" /* NOT_FOUND */,
|
|
1569
|
+
title,
|
|
1570
|
+
404,
|
|
1571
|
+
// HTTP 404 Not Found
|
|
1572
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
1573
|
+
details
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// src/errors/boundary.ts
|
|
1579
|
+
init_errors();
|
|
1580
|
+
init_correlation();
|
|
1581
|
+
init_internal_server_error();
|
|
1582
|
+
function isHandledError(error) {
|
|
1583
|
+
return error instanceof BlaizeError;
|
|
1584
|
+
}
|
|
1585
|
+
function formatErrorResponse(error) {
|
|
1586
|
+
if (isHandledError(error)) {
|
|
1587
|
+
return {
|
|
1588
|
+
type: error.type,
|
|
1589
|
+
title: error.title,
|
|
1590
|
+
status: error.status,
|
|
1591
|
+
correlationId: error.correlationId,
|
|
1592
|
+
timestamp: error.timestamp.toISOString(),
|
|
1593
|
+
details: error.details
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
const correlationId = generateCorrelationId();
|
|
1597
|
+
let originalMessage;
|
|
1598
|
+
if (error instanceof Error) {
|
|
1599
|
+
originalMessage = error.message;
|
|
1600
|
+
} else if (error === null || error === void 0) {
|
|
1601
|
+
originalMessage = "Unknown error occurred";
|
|
1602
|
+
} else {
|
|
1603
|
+
originalMessage = String(error);
|
|
1604
|
+
}
|
|
1605
|
+
const wrappedError = new InternalServerError(
|
|
1606
|
+
"Internal Server Error",
|
|
1607
|
+
{ originalMessage },
|
|
1608
|
+
correlationId
|
|
1609
|
+
);
|
|
1610
|
+
return {
|
|
1611
|
+
type: wrappedError.type,
|
|
1612
|
+
title: wrappedError.title,
|
|
1613
|
+
status: wrappedError.status,
|
|
1614
|
+
correlationId: wrappedError.correlationId,
|
|
1615
|
+
timestamp: wrappedError.timestamp.toISOString(),
|
|
1616
|
+
details: wrappedError.details
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function extractOrGenerateCorrelationId(headerGetter) {
|
|
1620
|
+
return headerGetter("x-correlation-id") ?? generateCorrelationId();
|
|
1621
|
+
}
|
|
1622
|
+
function setErrorResponseHeaders(headerSetter, correlationId) {
|
|
1623
|
+
headerSetter("x-correlation-id", correlationId);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/middleware/error-boundary.ts
|
|
1627
|
+
function createErrorBoundary(options = {}) {
|
|
1628
|
+
const { debug = false } = options;
|
|
1629
|
+
const middlewareFn = async (ctx, next) => {
|
|
1630
|
+
try {
|
|
1631
|
+
await next();
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
if (ctx.response.sent) {
|
|
1634
|
+
if (debug) {
|
|
1635
|
+
console.error("Error occurred after response was sent:", error);
|
|
1636
|
+
}
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
if (debug) {
|
|
1640
|
+
console.error("Error boundary caught error:", error);
|
|
1641
|
+
}
|
|
1642
|
+
const correlationId = extractOrGenerateCorrelationId(ctx.request.header);
|
|
1643
|
+
const errorResponse = formatErrorResponse(error);
|
|
1644
|
+
errorResponse.correlationId = correlationId;
|
|
1645
|
+
setErrorResponseHeaders(ctx.response.header, correlationId);
|
|
1646
|
+
ctx.response.status(errorResponse.status).json(errorResponse);
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
return {
|
|
1650
|
+
name: "ErrorBoundary",
|
|
1651
|
+
execute: middlewareFn,
|
|
1652
|
+
debug
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
|
|
774
1656
|
// src/server/request-handler.ts
|
|
775
1657
|
function createRequestHandler(serverInstance) {
|
|
776
1658
|
return async (req, res) => {
|
|
@@ -779,32 +1661,20 @@ function createRequestHandler(serverInstance) {
|
|
|
779
1661
|
parseBody: true
|
|
780
1662
|
// Enable automatic body parsing
|
|
781
1663
|
});
|
|
782
|
-
const
|
|
1664
|
+
const errorBoundary = createErrorBoundary();
|
|
1665
|
+
const allMiddleware = [errorBoundary, ...serverInstance.middleware];
|
|
1666
|
+
const handler = compose(allMiddleware);
|
|
783
1667
|
await runWithContext(context, async () => {
|
|
784
|
-
|
|
785
|
-
|
|
1668
|
+
await handler(context, async () => {
|
|
1669
|
+
if (!context.response.sent) {
|
|
1670
|
+
await serverInstance.router.handleRequest(context);
|
|
786
1671
|
if (!context.response.sent) {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
error: "Not Found",
|
|
791
|
-
message: `Route not found: ${context.request.method} ${context.request.path}`
|
|
792
|
-
});
|
|
793
|
-
}
|
|
1672
|
+
throw new NotFoundError(
|
|
1673
|
+
`Route not found: ${context.request.method} ${context.request.path}`
|
|
1674
|
+
);
|
|
794
1675
|
}
|
|
795
|
-
});
|
|
796
|
-
} catch (error) {
|
|
797
|
-
console.error("Error processing request:", error);
|
|
798
|
-
if (!context.response.sent) {
|
|
799
|
-
context.response.json(
|
|
800
|
-
{
|
|
801
|
-
error: "Internal Server Error",
|
|
802
|
-
message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
|
|
803
|
-
},
|
|
804
|
-
500
|
|
805
|
-
);
|
|
806
1676
|
}
|
|
807
|
-
}
|
|
1677
|
+
});
|
|
808
1678
|
});
|
|
809
1679
|
} catch (error) {
|
|
810
1680
|
console.error("Error creating context:", error);
|
|
@@ -861,7 +1731,7 @@ function createServerInstance(isHttp2, certOptions) {
|
|
|
861
1731
|
return http2.createSecureServer(http2ServerOptions);
|
|
862
1732
|
}
|
|
863
1733
|
function listenOnPort(server, port, host, isHttp2) {
|
|
864
|
-
return new Promise((
|
|
1734
|
+
return new Promise((resolve3, reject) => {
|
|
865
1735
|
server.listen(port, host, () => {
|
|
866
1736
|
const protocol = isHttp2 ? "https" : "http";
|
|
867
1737
|
const url = `${protocol}://${host}:${port}`;
|
|
@@ -878,7 +1748,7 @@ function listenOnPort(server, port, host, isHttp2) {
|
|
|
878
1748
|
|
|
879
1749
|
\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
|
|
880
1750
|
`);
|
|
881
|
-
|
|
1751
|
+
resolve3();
|
|
882
1752
|
});
|
|
883
1753
|
server.on("error", (err) => {
|
|
884
1754
|
console.error("Server error:", err);
|
|
@@ -922,55 +1792,128 @@ async function startServer(serverInstance, serverOptions) {
|
|
|
922
1792
|
}
|
|
923
1793
|
|
|
924
1794
|
// src/server/stop.ts
|
|
1795
|
+
var isShuttingDown = false;
|
|
925
1796
|
async function stopServer(serverInstance, options = {}) {
|
|
926
1797
|
const server = serverInstance.server;
|
|
927
1798
|
const events = serverInstance.events;
|
|
1799
|
+
if (isShuttingDown) {
|
|
1800
|
+
console.log("\u26A0\uFE0F Shutdown already in progress, ignoring duplicate shutdown request");
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
928
1803
|
if (!server) {
|
|
929
1804
|
return;
|
|
930
1805
|
}
|
|
931
|
-
|
|
1806
|
+
isShuttingDown = true;
|
|
1807
|
+
const timeout = options.timeout || 5e3;
|
|
932
1808
|
try {
|
|
933
1809
|
if (options.onStopping) {
|
|
934
1810
|
await options.onStopping();
|
|
935
1811
|
}
|
|
936
1812
|
events.emit("stopping");
|
|
937
|
-
|
|
1813
|
+
if (serverInstance.router && typeof serverInstance.router.close === "function") {
|
|
1814
|
+
console.log("\u{1F50C} Closing router watchers...");
|
|
1815
|
+
try {
|
|
1816
|
+
await Promise.race([
|
|
1817
|
+
serverInstance.router.close(),
|
|
1818
|
+
new Promise(
|
|
1819
|
+
(_, reject) => setTimeout(() => reject(new Error("Router close timeout")), 2e3)
|
|
1820
|
+
)
|
|
1821
|
+
]);
|
|
1822
|
+
console.log("\u2705 Router watchers closed");
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
console.error("\u274C Error closing router watchers:", error);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
try {
|
|
1828
|
+
await Promise.race([
|
|
1829
|
+
serverInstance.pluginManager.onServerStop(serverInstance, server),
|
|
1830
|
+
new Promise(
|
|
1831
|
+
(_, reject) => setTimeout(() => reject(new Error("Plugin stop timeout")), 2e3)
|
|
1832
|
+
)
|
|
1833
|
+
]);
|
|
1834
|
+
} catch (error) {
|
|
1835
|
+
console.error("\u274C Plugin stop timeout:", error);
|
|
1836
|
+
}
|
|
1837
|
+
const closePromise = new Promise((resolve3, reject) => {
|
|
1838
|
+
server.close((err) => {
|
|
1839
|
+
if (err) return reject(err);
|
|
1840
|
+
resolve3();
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
938
1843
|
const timeoutPromise = new Promise((_, reject) => {
|
|
939
1844
|
setTimeout(() => {
|
|
940
|
-
reject(new Error("Server shutdown
|
|
1845
|
+
reject(new Error("Server shutdown timeout"));
|
|
941
1846
|
}, timeout);
|
|
942
1847
|
});
|
|
943
|
-
const closePromise = new Promise((resolve2, reject) => {
|
|
944
|
-
server.close((err) => {
|
|
945
|
-
if (err) {
|
|
946
|
-
return reject(err);
|
|
947
|
-
}
|
|
948
|
-
resolve2();
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
1848
|
await Promise.race([closePromise, timeoutPromise]);
|
|
952
|
-
|
|
1849
|
+
try {
|
|
1850
|
+
await Promise.race([
|
|
1851
|
+
serverInstance.pluginManager.terminatePlugins(serverInstance),
|
|
1852
|
+
new Promise(
|
|
1853
|
+
(_, reject) => setTimeout(() => reject(new Error("Plugin terminate timeout")), 1e3)
|
|
1854
|
+
)
|
|
1855
|
+
]);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
console.error("\u274C Plugin terminate timeout:", error);
|
|
1858
|
+
}
|
|
953
1859
|
if (options.onStopped) {
|
|
954
1860
|
await options.onStopped();
|
|
955
1861
|
}
|
|
956
1862
|
events.emit("stopped");
|
|
957
1863
|
serverInstance.server = null;
|
|
1864
|
+
console.log("\u2705 Graceful shutdown completed");
|
|
1865
|
+
isShuttingDown = false;
|
|
958
1866
|
} catch (error) {
|
|
1867
|
+
isShuttingDown = false;
|
|
1868
|
+
console.error("\u26A0\uFE0F Shutdown error (forcing exit):", error);
|
|
1869
|
+
if (server && typeof server.close === "function") {
|
|
1870
|
+
server.close();
|
|
1871
|
+
}
|
|
1872
|
+
if (process.env.NODE_ENV === "development") {
|
|
1873
|
+
console.log("\u{1F504} Forcing exit for development restart...");
|
|
1874
|
+
process.exit(0);
|
|
1875
|
+
}
|
|
959
1876
|
events.emit("error", error);
|
|
960
1877
|
throw error;
|
|
961
1878
|
}
|
|
962
1879
|
}
|
|
963
1880
|
function registerSignalHandlers(stopFn) {
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1881
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
1882
|
+
if (isDevelopment) {
|
|
1883
|
+
const sigintHandler = () => {
|
|
1884
|
+
console.log("\u{1F4E4} SIGINT received, forcing exit for development restart...");
|
|
1885
|
+
process.exit(0);
|
|
1886
|
+
};
|
|
1887
|
+
const sigtermHandler = () => {
|
|
1888
|
+
console.log("\u{1F4E4} SIGTERM received, forcing exit for development restart...");
|
|
1889
|
+
process.exit(0);
|
|
1890
|
+
};
|
|
1891
|
+
process.on("SIGINT", sigintHandler);
|
|
1892
|
+
process.on("SIGTERM", sigtermHandler);
|
|
1893
|
+
return {
|
|
1894
|
+
unregister: () => {
|
|
1895
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
1896
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
} else {
|
|
1900
|
+
const sigintHandler = () => {
|
|
1901
|
+
console.log("\u{1F4E4} SIGINT received, starting graceful shutdown...");
|
|
1902
|
+
stopFn().catch(console.error);
|
|
1903
|
+
};
|
|
1904
|
+
const sigtermHandler = () => {
|
|
1905
|
+
console.log("\u{1F4E4} SIGTERM received, starting graceful shutdown...");
|
|
1906
|
+
stopFn().catch(console.error);
|
|
1907
|
+
};
|
|
1908
|
+
process.on("SIGINT", sigintHandler);
|
|
1909
|
+
process.on("SIGTERM", sigtermHandler);
|
|
1910
|
+
return {
|
|
1911
|
+
unregister: () => {
|
|
1912
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
1913
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
974
1917
|
}
|
|
975
1918
|
|
|
976
1919
|
// src/server/validation.ts
|
|
@@ -1176,59 +2119,33 @@ function validatePlugin(plugin, options = {}) {
|
|
|
1176
2119
|
}
|
|
1177
2120
|
}
|
|
1178
2121
|
|
|
1179
|
-
// src/router/discovery/
|
|
2122
|
+
// src/router/discovery/cache.ts
|
|
2123
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
1180
2124
|
var fs3 = __toESM(require("fs/promises"), 1);
|
|
2125
|
+
var import_node_module = require("module");
|
|
1181
2126
|
var path3 = __toESM(require("path"), 1);
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
2127
|
+
|
|
2128
|
+
// src/router/discovery/loader.ts
|
|
2129
|
+
async function dynamicImport(filePath) {
|
|
2130
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
2131
|
+
const importPath = filePath + cacheBuster;
|
|
1185
2132
|
try {
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
2133
|
+
const module2 = await import(importPath);
|
|
2134
|
+
console.log(`\u2705 Successfully imported module`);
|
|
2135
|
+
return module2;
|
|
1190
2136
|
} catch (error) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
throw error;
|
|
2137
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2138
|
+
console.log(`\u26A0\uFE0F Error importing with cache buster, trying original path:`, errorMessage);
|
|
2139
|
+
return import(filePath);
|
|
1195
2140
|
}
|
|
1196
|
-
const routeFiles = [];
|
|
1197
|
-
const ignore = options.ignore || ["node_modules", ".git"];
|
|
1198
|
-
async function scanDirectory(dir) {
|
|
1199
|
-
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
1200
|
-
for (const entry of entries) {
|
|
1201
|
-
const fullPath = path3.join(dir, entry.name);
|
|
1202
|
-
if (entry.isDirectory() && ignore.includes(entry.name)) {
|
|
1203
|
-
continue;
|
|
1204
|
-
}
|
|
1205
|
-
if (entry.isDirectory()) {
|
|
1206
|
-
await scanDirectory(fullPath);
|
|
1207
|
-
} else if (isRouteFile(entry.name)) {
|
|
1208
|
-
routeFiles.push(fullPath);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
await scanDirectory(absoluteDir);
|
|
1213
|
-
return routeFiles;
|
|
1214
|
-
}
|
|
1215
|
-
function isRouteFile(filename) {
|
|
1216
|
-
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// src/router/discovery/loader.ts
|
|
1220
|
-
async function dynamicImport(filePath) {
|
|
1221
|
-
return import(filePath);
|
|
1222
2141
|
}
|
|
1223
2142
|
async function loadRouteModule(filePath, basePath) {
|
|
1224
2143
|
try {
|
|
1225
2144
|
const parsedRoute = parseRoutePath(filePath, basePath);
|
|
1226
|
-
console.log("parsedRoute:", parsedRoute);
|
|
1227
2145
|
const module2 = await dynamicImport(filePath);
|
|
1228
|
-
console.log("Module exports:", Object.keys(module2));
|
|
2146
|
+
console.log("\u{1F4E6} Module exports:", Object.keys(module2));
|
|
1229
2147
|
const routes = [];
|
|
1230
2148
|
if (module2.default && typeof module2.default === "object") {
|
|
1231
|
-
console.log("Found default export:", module2.default);
|
|
1232
2149
|
const route = {
|
|
1233
2150
|
...module2.default,
|
|
1234
2151
|
path: parsedRoute.routePath
|
|
@@ -1241,7 +2158,6 @@ async function loadRouteModule(filePath, basePath) {
|
|
|
1241
2158
|
}
|
|
1242
2159
|
const potentialRoute = exportValue;
|
|
1243
2160
|
if (isValidRoute(potentialRoute)) {
|
|
1244
|
-
console.log(`Found named route export: ${exportName}`, potentialRoute);
|
|
1245
2161
|
const route = {
|
|
1246
2162
|
...potentialRoute,
|
|
1247
2163
|
// Use the route's own path if it has one, otherwise derive from file
|
|
@@ -1254,7 +2170,7 @@ async function loadRouteModule(filePath, basePath) {
|
|
|
1254
2170
|
console.warn(`Route file ${filePath} does not export any valid route definitions`);
|
|
1255
2171
|
return [];
|
|
1256
2172
|
}
|
|
1257
|
-
console.log(
|
|
2173
|
+
console.log(`\u2705 Successfully Loaded ${routes.length} route(s)`);
|
|
1258
2174
|
return routes;
|
|
1259
2175
|
} catch (error) {
|
|
1260
2176
|
console.error(`Failed to load route module ${filePath}:`, error);
|
|
@@ -1272,25 +2188,223 @@ function isValidRoute(obj) {
|
|
|
1272
2188
|
return hasHttpMethod;
|
|
1273
2189
|
}
|
|
1274
2190
|
|
|
1275
|
-
// src/router/discovery/
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
2191
|
+
// src/router/discovery/cache.ts
|
|
2192
|
+
var import_meta = {};
|
|
2193
|
+
var fileRouteCache = /* @__PURE__ */ new Map();
|
|
2194
|
+
async function processChangedFile(filePath, routesDir, updateCache = true) {
|
|
2195
|
+
const stat3 = await fs3.stat(filePath);
|
|
2196
|
+
const lastModified = stat3.mtime.getTime();
|
|
2197
|
+
const cachedEntry = fileRouteCache.get(filePath);
|
|
2198
|
+
if (updateCache && cachedEntry && cachedEntry.timestamp === lastModified) {
|
|
2199
|
+
return cachedEntry.routes;
|
|
2200
|
+
}
|
|
2201
|
+
invalidateModuleCache(filePath);
|
|
2202
|
+
const routes = await loadRouteModule(filePath, routesDir);
|
|
2203
|
+
if (updateCache) {
|
|
2204
|
+
const hash = hashRoutes(routes);
|
|
2205
|
+
fileRouteCache.set(filePath, {
|
|
2206
|
+
routes,
|
|
2207
|
+
timestamp: lastModified,
|
|
2208
|
+
hash
|
|
2209
|
+
});
|
|
1286
2210
|
}
|
|
1287
2211
|
return routes;
|
|
1288
2212
|
}
|
|
2213
|
+
function hasRouteContentChanged(filePath, newRoutes) {
|
|
2214
|
+
const cachedEntry = fileRouteCache.get(filePath);
|
|
2215
|
+
if (!cachedEntry) {
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2218
|
+
const newHash = hashRoutes(newRoutes);
|
|
2219
|
+
return cachedEntry.hash !== newHash;
|
|
2220
|
+
}
|
|
2221
|
+
function clearFileCache(filePath) {
|
|
2222
|
+
if (filePath) {
|
|
2223
|
+
fileRouteCache.delete(filePath);
|
|
2224
|
+
} else {
|
|
2225
|
+
fileRouteCache.clear();
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function hashRoutes(routes) {
|
|
2229
|
+
const routeData = routes.map((route) => ({
|
|
2230
|
+
path: route.path,
|
|
2231
|
+
methods: Object.keys(route).filter((key) => key !== "path").sort().map((method) => {
|
|
2232
|
+
const methodDef = route[method];
|
|
2233
|
+
const handlerString = methodDef?.handler ? methodDef.handler.toString() : null;
|
|
2234
|
+
return {
|
|
2235
|
+
method,
|
|
2236
|
+
// Include handler function string for change detection
|
|
2237
|
+
handler: handlerString,
|
|
2238
|
+
// Include middleware if present
|
|
2239
|
+
middleware: methodDef?.middleware ? methodDef.middleware.length : 0,
|
|
2240
|
+
// Include schema structure (but not full serialization which can be unstable)
|
|
2241
|
+
hasSchema: !!methodDef?.schema,
|
|
2242
|
+
schemaKeys: methodDef?.schema ? Object.keys(methodDef.schema).sort() : []
|
|
2243
|
+
};
|
|
2244
|
+
})
|
|
2245
|
+
}));
|
|
2246
|
+
const dataString = JSON.stringify(routeData);
|
|
2247
|
+
const hash = crypto.createHash("md5").update(dataString).digest("hex");
|
|
2248
|
+
return hash;
|
|
2249
|
+
}
|
|
2250
|
+
function invalidateModuleCache(filePath) {
|
|
2251
|
+
try {
|
|
2252
|
+
const absolutePath = path3.resolve(filePath);
|
|
2253
|
+
if (typeof require !== "undefined") {
|
|
2254
|
+
delete require.cache[absolutePath];
|
|
2255
|
+
try {
|
|
2256
|
+
const resolvedPath = require.resolve(absolutePath);
|
|
2257
|
+
delete require.cache[resolvedPath];
|
|
2258
|
+
} catch (resolveError) {
|
|
2259
|
+
const errorMessage = resolveError instanceof Error ? resolveError.message : String(resolveError);
|
|
2260
|
+
console.log(`\u26A0\uFE0F Could not resolve path: ${errorMessage}`);
|
|
2261
|
+
}
|
|
2262
|
+
} else {
|
|
2263
|
+
try {
|
|
2264
|
+
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
2265
|
+
delete require2.cache[absolutePath];
|
|
2266
|
+
try {
|
|
2267
|
+
const resolvedPath = require2.resolve(absolutePath);
|
|
2268
|
+
delete require2.cache[resolvedPath];
|
|
2269
|
+
} catch {
|
|
2270
|
+
console.log(`\u26A0\uFE0F Could not resolve ESM path`);
|
|
2271
|
+
}
|
|
2272
|
+
} catch {
|
|
2273
|
+
console.log(`\u26A0\uFE0F createRequire not available in pure ESM`);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
console.log(`\u26A0\uFE0F Error during module cache invalidation for ${filePath}:`, error);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
1289
2280
|
|
|
1290
|
-
// src/router/discovery/
|
|
2281
|
+
// src/router/discovery/parallel.ts
|
|
2282
|
+
var os = __toESM(require("os"), 1);
|
|
2283
|
+
|
|
2284
|
+
// src/router/discovery/finder.ts
|
|
2285
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
1291
2286
|
var path4 = __toESM(require("path"), 1);
|
|
2287
|
+
async function findRouteFiles(routesDir, options = {}) {
|
|
2288
|
+
const absoluteDir = path4.isAbsolute(routesDir) ? routesDir : path4.resolve(process.cwd(), routesDir);
|
|
2289
|
+
console.log("Creating router with routes directory:", absoluteDir);
|
|
2290
|
+
try {
|
|
2291
|
+
const stats = await fs4.stat(absoluteDir);
|
|
2292
|
+
if (!stats.isDirectory()) {
|
|
2293
|
+
throw new Error(`Route directory is not a directory: ${absoluteDir}`);
|
|
2294
|
+
}
|
|
2295
|
+
} catch (error) {
|
|
2296
|
+
if (error.code === "ENOENT") {
|
|
2297
|
+
throw new Error(`Route directory not found: ${absoluteDir}`);
|
|
2298
|
+
}
|
|
2299
|
+
throw error;
|
|
2300
|
+
}
|
|
2301
|
+
const routeFiles = [];
|
|
2302
|
+
const ignore = options.ignore || ["node_modules", ".git"];
|
|
2303
|
+
async function scanDirectory(dir) {
|
|
2304
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2305
|
+
for (const entry of entries) {
|
|
2306
|
+
const fullPath = path4.join(dir, entry.name);
|
|
2307
|
+
if (entry.isDirectory() && ignore.includes(entry.name)) {
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
if (entry.isDirectory()) {
|
|
2311
|
+
await scanDirectory(fullPath);
|
|
2312
|
+
} else if (isRouteFile(entry.name)) {
|
|
2313
|
+
routeFiles.push(fullPath);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
await scanDirectory(absoluteDir);
|
|
2318
|
+
return routeFiles;
|
|
2319
|
+
}
|
|
2320
|
+
function isRouteFile(filename) {
|
|
2321
|
+
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// src/router/discovery/parallel.ts
|
|
2325
|
+
async function processFilesInParallel(filePaths, processor, concurrency = Math.max(1, Math.floor(os.cpus().length / 2))) {
|
|
2326
|
+
const chunks = chunkArray(filePaths, concurrency);
|
|
2327
|
+
const results = [];
|
|
2328
|
+
for (const chunk of chunks) {
|
|
2329
|
+
const chunkResults = await Promise.allSettled(chunk.map((filePath) => processor(filePath)));
|
|
2330
|
+
const successfulResults = chunkResults.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
2331
|
+
results.push(...successfulResults);
|
|
2332
|
+
}
|
|
2333
|
+
return results;
|
|
2334
|
+
}
|
|
2335
|
+
async function loadInitialRoutesParallel(routesDir) {
|
|
2336
|
+
const files = await findRouteFiles(routesDir);
|
|
2337
|
+
const routeArrays = await processFilesInParallel(
|
|
2338
|
+
files,
|
|
2339
|
+
(filePath) => processChangedFile(filePath, routesDir)
|
|
2340
|
+
);
|
|
2341
|
+
return routeArrays.flat();
|
|
2342
|
+
}
|
|
2343
|
+
function chunkArray(array, chunkSize) {
|
|
2344
|
+
const chunks = [];
|
|
2345
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
2346
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
2347
|
+
}
|
|
2348
|
+
return chunks;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/router/discovery/profiler.ts
|
|
2352
|
+
var profilerState = {
|
|
2353
|
+
fileChanges: 0,
|
|
2354
|
+
totalReloadTime: 0,
|
|
2355
|
+
averageReloadTime: 0,
|
|
2356
|
+
slowReloads: []
|
|
2357
|
+
};
|
|
2358
|
+
function trackReloadPerformance(filePath, startTime) {
|
|
2359
|
+
const duration = Date.now() - startTime;
|
|
2360
|
+
profilerState.fileChanges++;
|
|
2361
|
+
profilerState.totalReloadTime += duration;
|
|
2362
|
+
profilerState.averageReloadTime = profilerState.totalReloadTime / profilerState.fileChanges;
|
|
2363
|
+
if (duration > 100) {
|
|
2364
|
+
profilerState.slowReloads.push({ file: filePath, time: duration });
|
|
2365
|
+
if (profilerState.slowReloads.length > 10) {
|
|
2366
|
+
profilerState.slowReloads.shift();
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
if (process.env.NODE_ENV === "development") {
|
|
2370
|
+
const emoji = duration < 50 ? "\u26A1" : duration < 100 ? "\u{1F504}" : "\u{1F40C}";
|
|
2371
|
+
console.log(`${emoji} Route reload: ${filePath} (${duration}ms)`);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
function withPerformanceTracking(fn, filePath) {
|
|
2375
|
+
console.log(`Tracking performance for: ${filePath}`);
|
|
2376
|
+
return async (...args) => {
|
|
2377
|
+
const startTime = Date.now();
|
|
2378
|
+
try {
|
|
2379
|
+
const result = await fn(...args);
|
|
2380
|
+
trackReloadPerformance(filePath, startTime);
|
|
2381
|
+
return result;
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
trackReloadPerformance(filePath, startTime);
|
|
2384
|
+
throw error;
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/router/discovery/watchers.ts
|
|
2390
|
+
var path5 = __toESM(require("path"), 1);
|
|
1292
2391
|
var import_chokidar = require("chokidar");
|
|
1293
2392
|
function watchRoutes(routesDir, options = {}) {
|
|
2393
|
+
const debounceMs = options.debounceMs || 16;
|
|
2394
|
+
const debouncedCallbacks = /* @__PURE__ */ new Map();
|
|
2395
|
+
function createDebouncedCallback(fn, filePath) {
|
|
2396
|
+
return (...args) => {
|
|
2397
|
+
const existingTimeout = debouncedCallbacks.get(filePath);
|
|
2398
|
+
if (existingTimeout) {
|
|
2399
|
+
clearTimeout(existingTimeout);
|
|
2400
|
+
}
|
|
2401
|
+
const timeoutId = setTimeout(() => {
|
|
2402
|
+
fn(...args);
|
|
2403
|
+
debouncedCallbacks.delete(filePath);
|
|
2404
|
+
}, debounceMs);
|
|
2405
|
+
debouncedCallbacks.set(filePath, timeoutId);
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
1294
2408
|
const routesByPath = /* @__PURE__ */ new Map();
|
|
1295
2409
|
async function loadInitialRoutes() {
|
|
1296
2410
|
try {
|
|
@@ -1306,28 +2420,34 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1306
2420
|
}
|
|
1307
2421
|
async function loadAndNotify(filePath) {
|
|
1308
2422
|
try {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
2423
|
+
const existingRoutes = routesByPath.get(filePath);
|
|
2424
|
+
const newRoutes = await processChangedFile(filePath, routesDir, false);
|
|
2425
|
+
if (!newRoutes || newRoutes.length === 0) {
|
|
1311
2426
|
return;
|
|
1312
2427
|
}
|
|
1313
|
-
|
|
2428
|
+
if (existingRoutes && !hasRouteContentChanged(filePath, newRoutes)) {
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
await processChangedFile(filePath, routesDir, true);
|
|
2432
|
+
const normalizedPath = path5.normalize(filePath);
|
|
1314
2433
|
if (existingRoutes) {
|
|
1315
|
-
routesByPath.set(filePath,
|
|
2434
|
+
routesByPath.set(filePath, newRoutes);
|
|
1316
2435
|
if (options.onRouteChanged) {
|
|
1317
|
-
options.onRouteChanged(
|
|
2436
|
+
options.onRouteChanged(normalizedPath, newRoutes);
|
|
1318
2437
|
}
|
|
1319
2438
|
} else {
|
|
1320
|
-
routesByPath.set(filePath,
|
|
2439
|
+
routesByPath.set(filePath, newRoutes);
|
|
1321
2440
|
if (options.onRouteAdded) {
|
|
1322
|
-
options.onRouteAdded(
|
|
2441
|
+
options.onRouteAdded(normalizedPath, newRoutes);
|
|
1323
2442
|
}
|
|
1324
2443
|
}
|
|
1325
2444
|
} catch (error) {
|
|
2445
|
+
console.log(`\u26A0\uFE0F Error processing file ${filePath}:`, error);
|
|
1326
2446
|
handleError(error);
|
|
1327
2447
|
}
|
|
1328
2448
|
}
|
|
1329
2449
|
function handleRemoved(filePath) {
|
|
1330
|
-
const normalizedPath =
|
|
2450
|
+
const normalizedPath = path5.normalize(filePath);
|
|
1331
2451
|
const routes = routesByPath.get(normalizedPath);
|
|
1332
2452
|
if (routes && routes.length > 0 && options.onRouteRemoved) {
|
|
1333
2453
|
options.onRouteRemoved(normalizedPath, routes);
|
|
@@ -1338,33 +2458,53 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1338
2458
|
if (options.onError && error instanceof Error) {
|
|
1339
2459
|
options.onError(error);
|
|
1340
2460
|
} else {
|
|
1341
|
-
console.error("Route watcher error:", error);
|
|
2461
|
+
console.error("\u26A0\uFE0F Route watcher error:", error);
|
|
1342
2462
|
}
|
|
1343
2463
|
}
|
|
1344
2464
|
const watcher = (0, import_chokidar.watch)(routesDir, {
|
|
2465
|
+
// Much faster response times
|
|
2466
|
+
awaitWriteFinish: {
|
|
2467
|
+
stabilityThreshold: 50,
|
|
2468
|
+
// Reduced from 300ms
|
|
2469
|
+
pollInterval: 10
|
|
2470
|
+
// Reduced from 100ms
|
|
2471
|
+
},
|
|
2472
|
+
// Performance optimizations
|
|
2473
|
+
usePolling: false,
|
|
2474
|
+
atomic: true,
|
|
2475
|
+
followSymlinks: false,
|
|
2476
|
+
depth: 10,
|
|
2477
|
+
// More aggressive ignoring
|
|
1345
2478
|
ignored: [
|
|
1346
2479
|
/(^|[/\\])\../,
|
|
1347
|
-
// Ignore dot files
|
|
1348
2480
|
/node_modules/,
|
|
2481
|
+
/\.git/,
|
|
2482
|
+
/\.DS_Store/,
|
|
2483
|
+
/Thumbs\.db/,
|
|
2484
|
+
/\.(test|spec)\.(ts|js)$/,
|
|
2485
|
+
/\.d\.ts$/,
|
|
2486
|
+
/\.map$/,
|
|
2487
|
+
/~$/,
|
|
1349
2488
|
...options.ignore || []
|
|
1350
|
-
]
|
|
1351
|
-
persistent: true,
|
|
1352
|
-
ignoreInitial: false,
|
|
1353
|
-
awaitWriteFinish: {
|
|
1354
|
-
stabilityThreshold: 300,
|
|
1355
|
-
pollInterval: 100
|
|
1356
|
-
}
|
|
2489
|
+
]
|
|
1357
2490
|
});
|
|
1358
|
-
watcher.on("add",
|
|
2491
|
+
watcher.on("add", (filePath) => {
|
|
2492
|
+
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
2493
|
+
debouncedLoad(filePath);
|
|
2494
|
+
}).on("change", (filePath) => {
|
|
2495
|
+
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
2496
|
+
debouncedLoad(filePath);
|
|
2497
|
+
}).on("unlink", (filePath) => {
|
|
2498
|
+
const debouncedRemove = createDebouncedCallback(handleRemoved, filePath);
|
|
2499
|
+
debouncedRemove(filePath);
|
|
2500
|
+
}).on("error", handleError);
|
|
1359
2501
|
loadInitialRoutes().catch(handleError);
|
|
1360
2502
|
return {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
* Get all currently loaded routes (flattened)
|
|
1367
|
-
*/
|
|
2503
|
+
close: () => {
|
|
2504
|
+
debouncedCallbacks.forEach((timeout) => clearTimeout(timeout));
|
|
2505
|
+
debouncedCallbacks.clear();
|
|
2506
|
+
return watcher.close();
|
|
2507
|
+
},
|
|
1368
2508
|
getRoutes: () => {
|
|
1369
2509
|
const allRoutes = [];
|
|
1370
2510
|
for (const routes of routesByPath.values()) {
|
|
@@ -1372,88 +2512,12 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1372
2512
|
}
|
|
1373
2513
|
return allRoutes;
|
|
1374
2514
|
},
|
|
1375
|
-
/**
|
|
1376
|
-
* Get routes organized by file path
|
|
1377
|
-
*/
|
|
1378
2515
|
getRoutesByFile: () => new Map(routesByPath)
|
|
1379
2516
|
};
|
|
1380
2517
|
}
|
|
1381
2518
|
|
|
1382
|
-
// src/router/
|
|
1383
|
-
|
|
1384
|
-
if (options.log) {
|
|
1385
|
-
console.error("Route error:", error);
|
|
1386
|
-
}
|
|
1387
|
-
const status = getErrorStatus(error);
|
|
1388
|
-
const response = {
|
|
1389
|
-
error: getErrorType(error),
|
|
1390
|
-
message: getErrorMessage(error)
|
|
1391
|
-
};
|
|
1392
|
-
if (options.detailed) {
|
|
1393
|
-
if (error instanceof Error) {
|
|
1394
|
-
response.stack = error.stack;
|
|
1395
|
-
}
|
|
1396
|
-
if (error && typeof error === "object" && "details" in error && error.details) {
|
|
1397
|
-
response.details = error.details;
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
ctx.response.status(status).json(response);
|
|
1401
|
-
}
|
|
1402
|
-
function getErrorStatus(error) {
|
|
1403
|
-
if (error && typeof error === "object") {
|
|
1404
|
-
if ("status" in error && typeof error.status === "number") {
|
|
1405
|
-
return error.status;
|
|
1406
|
-
}
|
|
1407
|
-
if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
1408
|
-
return error.statusCode;
|
|
1409
|
-
}
|
|
1410
|
-
if ("code" in error && typeof error.code === "string") {
|
|
1411
|
-
return getStatusFromCode(error.code);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
return 500;
|
|
1415
|
-
}
|
|
1416
|
-
function getStatusFromCode(code) {
|
|
1417
|
-
switch (code) {
|
|
1418
|
-
case "NOT_FOUND":
|
|
1419
|
-
return 404;
|
|
1420
|
-
case "UNAUTHORIZED":
|
|
1421
|
-
return 401;
|
|
1422
|
-
case "FORBIDDEN":
|
|
1423
|
-
return 403;
|
|
1424
|
-
case "BAD_REQUEST":
|
|
1425
|
-
return 400;
|
|
1426
|
-
case "CONFLICT":
|
|
1427
|
-
return 409;
|
|
1428
|
-
default:
|
|
1429
|
-
return 500;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
function getErrorType(error) {
|
|
1433
|
-
if (error && typeof error === "object") {
|
|
1434
|
-
if ("type" in error && typeof error.type === "string") {
|
|
1435
|
-
return error.type;
|
|
1436
|
-
}
|
|
1437
|
-
if ("name" in error && typeof error.name === "string") {
|
|
1438
|
-
return error.name;
|
|
1439
|
-
}
|
|
1440
|
-
if (error instanceof Error) {
|
|
1441
|
-
return error.constructor.name;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
return "Error";
|
|
1445
|
-
}
|
|
1446
|
-
function getErrorMessage(error) {
|
|
1447
|
-
if (error instanceof Error) {
|
|
1448
|
-
return error.message;
|
|
1449
|
-
}
|
|
1450
|
-
if (error && typeof error === "object") {
|
|
1451
|
-
if ("message" in error && typeof error.message === "string") {
|
|
1452
|
-
return error.message;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
return String(error);
|
|
1456
|
-
}
|
|
2519
|
+
// src/router/validation/schema.ts
|
|
2520
|
+
var import_zod6 = require("zod");
|
|
1457
2521
|
|
|
1458
2522
|
// src/router/validation/body.ts
|
|
1459
2523
|
var import_zod2 = require("zod");
|
|
@@ -1492,37 +2556,49 @@ function validateResponse(response, schema) {
|
|
|
1492
2556
|
}
|
|
1493
2557
|
|
|
1494
2558
|
// src/router/validation/schema.ts
|
|
2559
|
+
init_internal_server_error();
|
|
2560
|
+
init_validation_error();
|
|
1495
2561
|
function createRequestValidator(schema, debug = false) {
|
|
1496
2562
|
const middlewareFn = async (ctx, next) => {
|
|
1497
|
-
const errors = {};
|
|
1498
2563
|
if (schema.params && ctx.request.params) {
|
|
1499
2564
|
try {
|
|
1500
2565
|
ctx.request.params = validateParams(ctx.request.params, schema.params);
|
|
1501
2566
|
} catch (error) {
|
|
1502
|
-
|
|
2567
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2568
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2569
|
+
throw new ValidationError("Request validation failed", {
|
|
2570
|
+
fields: fieldErrors,
|
|
2571
|
+
errorCount,
|
|
2572
|
+
section: "params"
|
|
2573
|
+
});
|
|
1503
2574
|
}
|
|
1504
2575
|
}
|
|
1505
2576
|
if (schema.query && ctx.request.query) {
|
|
1506
2577
|
try {
|
|
1507
2578
|
ctx.request.query = validateQuery(ctx.request.query, schema.query);
|
|
1508
2579
|
} catch (error) {
|
|
1509
|
-
|
|
2580
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2581
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2582
|
+
throw new ValidationError("Request validation failed", {
|
|
2583
|
+
fields: fieldErrors,
|
|
2584
|
+
errorCount,
|
|
2585
|
+
section: "query"
|
|
2586
|
+
});
|
|
1510
2587
|
}
|
|
1511
2588
|
}
|
|
1512
2589
|
if (schema.body) {
|
|
1513
2590
|
try {
|
|
1514
2591
|
ctx.request.body = validateBody(ctx.request.body, schema.body);
|
|
1515
2592
|
} catch (error) {
|
|
1516
|
-
|
|
2593
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2594
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2595
|
+
throw new ValidationError("Request validation failed", {
|
|
2596
|
+
fields: fieldErrors,
|
|
2597
|
+
errorCount,
|
|
2598
|
+
section: "body"
|
|
2599
|
+
});
|
|
1517
2600
|
}
|
|
1518
2601
|
}
|
|
1519
|
-
if (Object.keys(errors).length > 0) {
|
|
1520
|
-
ctx.response.status(400).json({
|
|
1521
|
-
error: "Validation Error",
|
|
1522
|
-
details: errors
|
|
1523
|
-
});
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
2602
|
await next();
|
|
1527
2603
|
};
|
|
1528
2604
|
return {
|
|
@@ -1541,12 +2617,11 @@ function createResponseValidator(responseSchema, debug = false) {
|
|
|
1541
2617
|
return originalJson.call(ctx.response, validatedBody, status);
|
|
1542
2618
|
} catch (error) {
|
|
1543
2619
|
ctx.response.json = originalJson;
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
2620
|
+
throw new InternalServerError("Response validation failed", {
|
|
2621
|
+
responseSchema: responseSchema.description || "Unknown schema",
|
|
2622
|
+
validationError: extractZodFieldErrors(error),
|
|
2623
|
+
originalResponse: body
|
|
1548
2624
|
});
|
|
1549
|
-
return ctx.response;
|
|
1550
2625
|
}
|
|
1551
2626
|
};
|
|
1552
2627
|
await next();
|
|
@@ -1557,11 +2632,25 @@ function createResponseValidator(responseSchema, debug = false) {
|
|
|
1557
2632
|
debug
|
|
1558
2633
|
};
|
|
1559
2634
|
}
|
|
1560
|
-
function
|
|
1561
|
-
if (error
|
|
1562
|
-
|
|
2635
|
+
function extractZodFieldErrors(error) {
|
|
2636
|
+
if (error instanceof import_zod6.z.ZodError) {
|
|
2637
|
+
const fieldErrorMap = /* @__PURE__ */ new Map();
|
|
2638
|
+
for (const issue of error.issues) {
|
|
2639
|
+
const fieldPath = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2640
|
+
if (!fieldErrorMap.has(fieldPath)) {
|
|
2641
|
+
fieldErrorMap.set(fieldPath, []);
|
|
2642
|
+
}
|
|
2643
|
+
fieldErrorMap.get(fieldPath).push(issue.message);
|
|
2644
|
+
}
|
|
2645
|
+
return Array.from(fieldErrorMap.entries()).map(([field, messages]) => ({
|
|
2646
|
+
field,
|
|
2647
|
+
messages
|
|
2648
|
+
}));
|
|
2649
|
+
}
|
|
2650
|
+
if (error instanceof Error) {
|
|
2651
|
+
return [{ field: "unknown", messages: [error.message] }];
|
|
1563
2652
|
}
|
|
1564
|
-
return
|
|
2653
|
+
return [{ field: "unknown", messages: [String(error)] }];
|
|
1565
2654
|
}
|
|
1566
2655
|
|
|
1567
2656
|
// src/router/handlers/executor.ts
|
|
@@ -1585,8 +2674,8 @@ async function executeHandler(ctx, routeOptions, params) {
|
|
|
1585
2674
|
}
|
|
1586
2675
|
|
|
1587
2676
|
// src/router/matching/params.ts
|
|
1588
|
-
function extractParams(
|
|
1589
|
-
const match = pattern.exec(
|
|
2677
|
+
function extractParams(path6, pattern, paramNames) {
|
|
2678
|
+
const match = pattern.exec(path6);
|
|
1590
2679
|
if (!match) {
|
|
1591
2680
|
return {};
|
|
1592
2681
|
}
|
|
@@ -1596,15 +2685,15 @@ function extractParams(path5, pattern, paramNames) {
|
|
|
1596
2685
|
}
|
|
1597
2686
|
return params;
|
|
1598
2687
|
}
|
|
1599
|
-
function compilePathPattern(
|
|
2688
|
+
function compilePathPattern(path6) {
|
|
1600
2689
|
const paramNames = [];
|
|
1601
|
-
if (
|
|
2690
|
+
if (path6 === "/") {
|
|
1602
2691
|
return {
|
|
1603
2692
|
pattern: /^\/$/,
|
|
1604
2693
|
paramNames: []
|
|
1605
2694
|
};
|
|
1606
2695
|
}
|
|
1607
|
-
let patternString =
|
|
2696
|
+
let patternString = path6.replace(/([.+*?^$(){}|\\])/g, "\\$1");
|
|
1608
2697
|
patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
|
|
1609
2698
|
paramNames.push(paramName);
|
|
1610
2699
|
return "/([^/]+)";
|
|
@@ -1627,10 +2716,10 @@ function createMatcher() {
|
|
|
1627
2716
|
/**
|
|
1628
2717
|
* Add a route to the matcher
|
|
1629
2718
|
*/
|
|
1630
|
-
add(
|
|
1631
|
-
const { pattern, paramNames } = compilePathPattern(
|
|
2719
|
+
add(path6, method, routeOptions) {
|
|
2720
|
+
const { pattern, paramNames } = compilePathPattern(path6);
|
|
1632
2721
|
const newRoute = {
|
|
1633
|
-
path:
|
|
2722
|
+
path: path6,
|
|
1634
2723
|
method,
|
|
1635
2724
|
pattern,
|
|
1636
2725
|
paramNames,
|
|
@@ -1643,17 +2732,33 @@ function createMatcher() {
|
|
|
1643
2732
|
routes.splice(insertIndex, 0, newRoute);
|
|
1644
2733
|
}
|
|
1645
2734
|
},
|
|
2735
|
+
/**
|
|
2736
|
+
* Remove a route from the matcher by path
|
|
2737
|
+
*/
|
|
2738
|
+
remove(path6) {
|
|
2739
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
2740
|
+
if (routes[i].path === path6) {
|
|
2741
|
+
routes.splice(i, 1);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
},
|
|
2745
|
+
/**
|
|
2746
|
+
* Clear all routes from the matcher
|
|
2747
|
+
*/
|
|
2748
|
+
clear() {
|
|
2749
|
+
routes.length = 0;
|
|
2750
|
+
},
|
|
1646
2751
|
/**
|
|
1647
2752
|
* Match a URL path to a route
|
|
1648
2753
|
*/
|
|
1649
|
-
match(
|
|
1650
|
-
const pathname =
|
|
2754
|
+
match(path6, method) {
|
|
2755
|
+
const pathname = path6.split("?")[0];
|
|
1651
2756
|
if (!pathname) return null;
|
|
1652
2757
|
for (const route of routes) {
|
|
1653
2758
|
if (route.method !== method) continue;
|
|
1654
2759
|
const match = route.pattern.exec(pathname);
|
|
1655
2760
|
if (match) {
|
|
1656
|
-
const params = extractParams(
|
|
2761
|
+
const params = extractParams(path6, route.pattern, route.paramNames);
|
|
1657
2762
|
return {
|
|
1658
2763
|
route: route.routeOptions,
|
|
1659
2764
|
params
|
|
@@ -1661,14 +2766,14 @@ function createMatcher() {
|
|
|
1661
2766
|
}
|
|
1662
2767
|
}
|
|
1663
2768
|
const matchingPath = routes.find(
|
|
1664
|
-
(route) => route.method !== method && route.pattern.test(
|
|
2769
|
+
(route) => route.method !== method && route.pattern.test(path6)
|
|
1665
2770
|
);
|
|
1666
2771
|
if (matchingPath) {
|
|
1667
2772
|
return {
|
|
1668
2773
|
route: null,
|
|
1669
2774
|
params: {},
|
|
1670
2775
|
methodNotAllowed: true,
|
|
1671
|
-
allowedMethods: routes.filter((route) => route.pattern.test(
|
|
2776
|
+
allowedMethods: routes.filter((route) => route.pattern.test(path6)).map((route) => route.method)
|
|
1672
2777
|
};
|
|
1673
2778
|
}
|
|
1674
2779
|
return null;
|
|
@@ -1685,16 +2790,93 @@ function createMatcher() {
|
|
|
1685
2790
|
/**
|
|
1686
2791
|
* Find routes matching a specific path
|
|
1687
2792
|
*/
|
|
1688
|
-
findRoutes(
|
|
1689
|
-
return routes.filter((route) => route.pattern.test(
|
|
2793
|
+
findRoutes(path6) {
|
|
2794
|
+
return routes.filter((route) => route.pattern.test(path6)).map((route) => ({
|
|
1690
2795
|
path: route.path,
|
|
1691
2796
|
method: route.method,
|
|
1692
|
-
params: extractParams(
|
|
2797
|
+
params: extractParams(path6, route.pattern, route.paramNames)
|
|
1693
2798
|
}));
|
|
1694
2799
|
}
|
|
1695
2800
|
};
|
|
1696
2801
|
}
|
|
1697
2802
|
|
|
2803
|
+
// src/router/registry/fast-registry.ts
|
|
2804
|
+
function createRouteRegistry() {
|
|
2805
|
+
return {
|
|
2806
|
+
routesByPath: /* @__PURE__ */ new Map(),
|
|
2807
|
+
routesByFile: /* @__PURE__ */ new Map(),
|
|
2808
|
+
pathToFile: /* @__PURE__ */ new Map()
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
function updateRoutesFromFile(registry, filePath, newRoutes) {
|
|
2812
|
+
console.log(`Updating routes from file: ${filePath}`);
|
|
2813
|
+
const oldPaths = registry.routesByFile.get(filePath) || /* @__PURE__ */ new Set();
|
|
2814
|
+
const newPaths = new Set(newRoutes.map((r) => r.path));
|
|
2815
|
+
const added = newRoutes.filter((r) => !oldPaths.has(r.path));
|
|
2816
|
+
const removed = Array.from(oldPaths).filter((p) => !newPaths.has(p));
|
|
2817
|
+
const potentiallyChanged = newRoutes.filter((r) => oldPaths.has(r.path));
|
|
2818
|
+
const changed = potentiallyChanged.filter((route) => {
|
|
2819
|
+
const existingRoute = registry.routesByPath.get(route.path);
|
|
2820
|
+
return !existingRoute || !routesEqual(existingRoute, route);
|
|
2821
|
+
});
|
|
2822
|
+
applyRouteUpdates(registry, filePath, { added, removed, changed });
|
|
2823
|
+
return { added, removed, changed };
|
|
2824
|
+
}
|
|
2825
|
+
function getAllRoutesFromRegistry(registry) {
|
|
2826
|
+
return Array.from(registry.routesByPath.values());
|
|
2827
|
+
}
|
|
2828
|
+
function applyRouteUpdates(registry, filePath, updates) {
|
|
2829
|
+
const { added, removed, changed } = updates;
|
|
2830
|
+
removed.forEach((path6) => {
|
|
2831
|
+
registry.routesByPath.delete(path6);
|
|
2832
|
+
registry.pathToFile.delete(path6);
|
|
2833
|
+
});
|
|
2834
|
+
[...added, ...changed].forEach((route) => {
|
|
2835
|
+
registry.routesByPath.set(route.path, route);
|
|
2836
|
+
registry.pathToFile.set(route.path, filePath);
|
|
2837
|
+
});
|
|
2838
|
+
const allPathsForFile = /* @__PURE__ */ new Set([
|
|
2839
|
+
...added.map((r) => r.path),
|
|
2840
|
+
...changed.map((r) => r.path),
|
|
2841
|
+
...Array.from(registry.routesByFile.get(filePath) || []).filter((p) => !removed.includes(p))
|
|
2842
|
+
]);
|
|
2843
|
+
if (allPathsForFile.size > 0) {
|
|
2844
|
+
registry.routesByFile.set(filePath, allPathsForFile);
|
|
2845
|
+
} else {
|
|
2846
|
+
registry.routesByFile.delete(filePath);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function routesEqual(route1, route2) {
|
|
2850
|
+
if (route1.path !== route2.path) return false;
|
|
2851
|
+
const methods1 = Object.keys(route1).filter((k) => k !== "path").sort();
|
|
2852
|
+
const methods2 = Object.keys(route2).filter((k) => k !== "path").sort();
|
|
2853
|
+
if (methods1.length !== methods2.length) return false;
|
|
2854
|
+
return methods1.every((method) => {
|
|
2855
|
+
const handler1 = route1[method];
|
|
2856
|
+
const handler2 = route2[method];
|
|
2857
|
+
return typeof handler1 === typeof handler2;
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// src/router/utils/matching-helpers.ts
|
|
2862
|
+
function addRouteToMatcher(route, matcher) {
|
|
2863
|
+
Object.entries(route).forEach(([method, methodOptions]) => {
|
|
2864
|
+
if (method === "path" || !methodOptions) return;
|
|
2865
|
+
matcher.add(route.path, method, methodOptions);
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
function removeRouteFromMatcher(path6, matcher) {
|
|
2869
|
+
if ("remove" in matcher && typeof matcher.remove === "function") {
|
|
2870
|
+
matcher.remove(path6);
|
|
2871
|
+
} else {
|
|
2872
|
+
console.warn("Matcher does not support selective removal, consider adding remove() method");
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function updateRouteInMatcher(route, matcher) {
|
|
2876
|
+
removeRouteFromMatcher(route.path, matcher);
|
|
2877
|
+
addRouteToMatcher(route, matcher);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
1698
2880
|
// src/router/router.ts
|
|
1699
2881
|
var DEFAULT_ROUTER_OPTIONS = {
|
|
1700
2882
|
routesDir: "./routes",
|
|
@@ -1709,46 +2891,55 @@ function createRouter(options) {
|
|
|
1709
2891
|
if (options.basePath && !options.basePath.startsWith("/")) {
|
|
1710
2892
|
console.warn("Base path does nothing");
|
|
1711
2893
|
}
|
|
1712
|
-
const
|
|
2894
|
+
const registry = createRouteRegistry();
|
|
1713
2895
|
const matcher = createMatcher();
|
|
1714
2896
|
let initialized = false;
|
|
1715
2897
|
let initializationPromise = null;
|
|
1716
2898
|
let _watchers = null;
|
|
1717
|
-
const routeSources = /* @__PURE__ */ new Map();
|
|
1718
2899
|
const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
|
|
1719
|
-
function
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
2900
|
+
function applyMatcherChanges(changes) {
|
|
2901
|
+
console.log("\n\u{1F527} APPLYING MATCHER CHANGES:");
|
|
2902
|
+
console.log(` Adding ${changes.added.length} routes`);
|
|
2903
|
+
console.log(` Removing ${changes.removed.length} routes`);
|
|
2904
|
+
console.log(` Updating ${changes.changed.length} routes`);
|
|
2905
|
+
changes.removed.forEach((routePath) => {
|
|
2906
|
+
console.log(` \u2796 Removing: ${routePath}`);
|
|
2907
|
+
removeRouteFromMatcher(routePath, matcher);
|
|
2908
|
+
});
|
|
2909
|
+
changes.added.forEach((route) => {
|
|
2910
|
+
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2911
|
+
console.log(` \u2795 Adding: ${route.path} [${methods.join(", ")}]`);
|
|
2912
|
+
addRouteToMatcher(route, matcher);
|
|
2913
|
+
});
|
|
2914
|
+
changes.changed.forEach((route) => {
|
|
2915
|
+
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2916
|
+
console.log(` \u{1F504} Updating: ${route.path} [${methods.join(", ")}]`);
|
|
2917
|
+
updateRouteInMatcher(route, matcher);
|
|
2918
|
+
});
|
|
2919
|
+
console.log("\u2705 Matcher changes applied\n");
|
|
2920
|
+
}
|
|
2921
|
+
function addRoutesWithSource(routes, source) {
|
|
2922
|
+
try {
|
|
2923
|
+
const changes = updateRoutesFromFile(registry, source, routes);
|
|
2924
|
+
applyMatcherChanges(changes);
|
|
2925
|
+
return changes;
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
console.error(`\u26A0\uFE0F Route conflicts from ${source}:`, error);
|
|
2928
|
+
throw error;
|
|
1731
2929
|
}
|
|
1732
|
-
routeSources.set(route.path, [...existingSources, source]);
|
|
1733
|
-
addRouteInternal(route);
|
|
1734
2930
|
}
|
|
1735
2931
|
async function loadRoutesFromDirectory(directory, source, prefix) {
|
|
1736
2932
|
try {
|
|
1737
|
-
const discoveredRoutes = await
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
...route,
|
|
1743
|
-
path: `${prefix}${route.path}`
|
|
1744
|
-
} : route;
|
|
1745
|
-
addRouteWithSource(finalRoute, source);
|
|
1746
|
-
}
|
|
2933
|
+
const discoveredRoutes = await loadInitialRoutesParallel(directory);
|
|
2934
|
+
const finalRoutes = discoveredRoutes.map(
|
|
2935
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2936
|
+
);
|
|
2937
|
+
const changes = addRoutesWithSource(finalRoutes, source);
|
|
1747
2938
|
console.log(
|
|
1748
|
-
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
|
|
2939
|
+
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""} (${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed)`
|
|
1749
2940
|
);
|
|
1750
2941
|
} catch (error) {
|
|
1751
|
-
console.error(
|
|
2942
|
+
console.error(`\u26A0\uFE0F Failed to load routes from ${source}:`, error);
|
|
1752
2943
|
throw error;
|
|
1753
2944
|
}
|
|
1754
2945
|
}
|
|
@@ -1758,105 +2949,126 @@ function createRouter(options) {
|
|
|
1758
2949
|
}
|
|
1759
2950
|
initializationPromise = (async () => {
|
|
1760
2951
|
try {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2952
|
+
await Promise.all(
|
|
2953
|
+
Array.from(routeDirectories).map(
|
|
2954
|
+
(directory) => loadRoutesFromDirectory(directory, directory)
|
|
2955
|
+
)
|
|
2956
|
+
);
|
|
1764
2957
|
if (routerOptions.watchMode) {
|
|
1765
|
-
|
|
2958
|
+
setupOptimizedWatching();
|
|
1766
2959
|
}
|
|
1767
2960
|
initialized = true;
|
|
1768
2961
|
} catch (error) {
|
|
1769
|
-
console.error("Failed to initialize router:", error);
|
|
2962
|
+
console.error("\u26A0\uFE0F Failed to initialize router:", error);
|
|
1770
2963
|
throw error;
|
|
1771
2964
|
}
|
|
1772
2965
|
})();
|
|
1773
2966
|
return initializationPromise;
|
|
1774
2967
|
}
|
|
1775
|
-
function
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
addRouteWithSource(finalRoute, source);
|
|
1792
|
-
});
|
|
1793
|
-
},
|
|
1794
|
-
onRouteChanged: (changedRoutes) => {
|
|
1795
|
-
console.log(
|
|
1796
|
-
`${changedRoutes.length} route(s) changed in ${directory}:`,
|
|
1797
|
-
changedRoutes.map((r) => r.path)
|
|
1798
|
-
);
|
|
1799
|
-
changedRoutes.forEach((route) => {
|
|
1800
|
-
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
1801
|
-
const index = routes.findIndex((r) => r.path === finalPath);
|
|
1802
|
-
if (index >= 0) {
|
|
1803
|
-
routes.splice(index, 1);
|
|
1804
|
-
const sources = routeSources.get(finalPath) || [];
|
|
1805
|
-
const filteredSources = sources.filter((s) => s !== source);
|
|
1806
|
-
if (filteredSources.length > 0) {
|
|
1807
|
-
routeSources.set(finalPath, filteredSources);
|
|
1808
|
-
} else {
|
|
1809
|
-
routeSources.delete(finalPath);
|
|
2968
|
+
function setupOptimizedWatching() {
|
|
2969
|
+
if (!_watchers) {
|
|
2970
|
+
_watchers = /* @__PURE__ */ new Map();
|
|
2971
|
+
}
|
|
2972
|
+
for (const directory of routeDirectories) {
|
|
2973
|
+
if (!_watchers.has(directory)) {
|
|
2974
|
+
const watcher = watchRoutes(directory, {
|
|
2975
|
+
debounceMs: 16,
|
|
2976
|
+
// ~60fps debouncing
|
|
2977
|
+
ignore: ["node_modules", ".git"],
|
|
2978
|
+
onRouteAdded: (filepath, addedRoutes) => {
|
|
2979
|
+
try {
|
|
2980
|
+
const changes = updateRoutesFromFile(registry, filepath, addedRoutes);
|
|
2981
|
+
applyMatcherChanges(changes);
|
|
2982
|
+
} catch (error) {
|
|
2983
|
+
console.error(`Error adding routes from ${directory}:`, error);
|
|
1810
2984
|
}
|
|
2985
|
+
},
|
|
2986
|
+
onRouteChanged: withPerformanceTracking(
|
|
2987
|
+
async (filepath, changedRoutes) => {
|
|
2988
|
+
try {
|
|
2989
|
+
console.log(`Processing changes for ${filepath}`);
|
|
2990
|
+
const changes = updateRoutesFromFile(registry, filepath, changedRoutes);
|
|
2991
|
+
console.log(
|
|
2992
|
+
`Changes detected: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2993
|
+
);
|
|
2994
|
+
applyMatcherChanges(changes);
|
|
2995
|
+
console.log(
|
|
2996
|
+
`Route changes applied: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2997
|
+
);
|
|
2998
|
+
} catch (error) {
|
|
2999
|
+
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
3000
|
+
}
|
|
3001
|
+
},
|
|
3002
|
+
directory
|
|
3003
|
+
),
|
|
3004
|
+
onRouteRemoved: (filePath, removedRoutes) => {
|
|
3005
|
+
console.log(`File removed: ${filePath} with ${removedRoutes.length} routes`);
|
|
3006
|
+
try {
|
|
3007
|
+
removedRoutes.forEach((route) => {
|
|
3008
|
+
removeRouteFromMatcher(route.path, matcher);
|
|
3009
|
+
});
|
|
3010
|
+
clearFileCache(filePath);
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
console.error(`\u26A0\uFE0F Error removing routes from ${filePath}:`, error);
|
|
3013
|
+
}
|
|
3014
|
+
},
|
|
3015
|
+
onError: (error) => {
|
|
3016
|
+
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
1811
3017
|
}
|
|
1812
|
-
const finalRoute = prefix ? { ...route, path: finalPath } : route;
|
|
1813
|
-
addRouteWithSource(finalRoute, source);
|
|
1814
3018
|
});
|
|
3019
|
+
_watchers.set(directory, watcher);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
function setupWatcherForNewDirectory(directory, prefix) {
|
|
3024
|
+
if (!_watchers) {
|
|
3025
|
+
_watchers = /* @__PURE__ */ new Map();
|
|
3026
|
+
}
|
|
3027
|
+
const watcher = watchRoutes(directory, {
|
|
3028
|
+
debounceMs: 16,
|
|
3029
|
+
ignore: ["node_modules", ".git"],
|
|
3030
|
+
onRouteAdded: (filePath, addedRoutes) => {
|
|
3031
|
+
try {
|
|
3032
|
+
const finalRoutes = addedRoutes.map(
|
|
3033
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
3034
|
+
);
|
|
3035
|
+
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
3036
|
+
applyMatcherChanges(changes);
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
console.error(`\u26A0\uFE0F Error adding routes from ${directory}:`, error);
|
|
3039
|
+
}
|
|
1815
3040
|
},
|
|
3041
|
+
onRouteChanged: withPerformanceTracking(async (filePath, changedRoutes) => {
|
|
3042
|
+
try {
|
|
3043
|
+
const finalRoutes = changedRoutes.map(
|
|
3044
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
3045
|
+
);
|
|
3046
|
+
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
3047
|
+
applyMatcherChanges(changes);
|
|
3048
|
+
} catch (error) {
|
|
3049
|
+
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
3050
|
+
}
|
|
3051
|
+
}, directory),
|
|
1816
3052
|
onRouteRemoved: (filePath, removedRoutes) => {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
const sources = routeSources.get(finalPath) || [];
|
|
1828
|
-
const filteredSources = sources.filter((s) => s !== source);
|
|
1829
|
-
if (filteredSources.length > 0) {
|
|
1830
|
-
routeSources.set(finalPath, filteredSources);
|
|
1831
|
-
} else {
|
|
1832
|
-
routeSources.delete(finalPath);
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
3053
|
+
try {
|
|
3054
|
+
removedRoutes.forEach((route) => {
|
|
3055
|
+
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
3056
|
+
removeRouteFromMatcher(finalPath, matcher);
|
|
3057
|
+
});
|
|
3058
|
+
clearFileCache(filePath);
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
console.error(`Error removing routes from ${filePath}:`, error);
|
|
3061
|
+
}
|
|
1835
3062
|
},
|
|
1836
3063
|
onError: (error) => {
|
|
1837
|
-
console.error(
|
|
3064
|
+
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
1838
3065
|
}
|
|
1839
|
-
};
|
|
1840
|
-
}
|
|
1841
|
-
function setupWatcherForDirectory(directory, source, prefix) {
|
|
1842
|
-
const callbacks = createWatcherCallbacks(directory, source, prefix);
|
|
1843
|
-
const watcher = watchRoutes(directory, {
|
|
1844
|
-
ignore: ["node_modules", ".git"],
|
|
1845
|
-
...callbacks
|
|
1846
3066
|
});
|
|
1847
|
-
if (!_watchers) {
|
|
1848
|
-
_watchers = /* @__PURE__ */ new Map();
|
|
1849
|
-
}
|
|
1850
3067
|
_watchers.set(directory, watcher);
|
|
1851
3068
|
return watcher;
|
|
1852
3069
|
}
|
|
1853
|
-
function setupWatcherForAllDirectories() {
|
|
1854
|
-
for (const directory of routeDirectories) {
|
|
1855
|
-
setupWatcherForDirectory(directory, directory);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
3070
|
initialize().catch((error) => {
|
|
1859
|
-
console.error("Failed to initialize router on creation:", error);
|
|
3071
|
+
console.error("\u26A0\uFE0F Failed to initialize router on creation:", error);
|
|
1860
3072
|
});
|
|
1861
3073
|
return {
|
|
1862
3074
|
/**
|
|
@@ -1864,17 +3076,22 @@ function createRouter(options) {
|
|
|
1864
3076
|
*/
|
|
1865
3077
|
async handleRequest(ctx) {
|
|
1866
3078
|
if (!initialized) {
|
|
3079
|
+
console.log("\u{1F504} Router not initialized, initializing...");
|
|
1867
3080
|
await initialize();
|
|
1868
3081
|
}
|
|
1869
|
-
const { method, path:
|
|
1870
|
-
|
|
3082
|
+
const { method, path: path6 } = ctx.request;
|
|
3083
|
+
console.log(`
|
|
3084
|
+
\u{1F4E5} Handling request: ${method} ${path6}`);
|
|
3085
|
+
const match = matcher.match(path6, method);
|
|
1871
3086
|
if (!match) {
|
|
1872
|
-
|
|
1873
|
-
|
|
3087
|
+
console.log(`\u274C No match found for: ${method} ${path6}`);
|
|
3088
|
+
throw new NotFoundError("Not found");
|
|
1874
3089
|
}
|
|
3090
|
+
console.log(`\u2705 Route matched: ${method} ${path6}`);
|
|
3091
|
+
console.log(` Params: ${JSON.stringify(match.params)}`);
|
|
1875
3092
|
if (match.methodNotAllowed) {
|
|
1876
3093
|
ctx.response.status(405).json({
|
|
1877
|
-
error: "Method Not Allowed",
|
|
3094
|
+
error: "\u274C Method Not Allowed",
|
|
1878
3095
|
allowed: match.allowedMethods
|
|
1879
3096
|
});
|
|
1880
3097
|
if (match.allowedMethods && match.allowedMethods.length > 0) {
|
|
@@ -1883,29 +3100,31 @@ function createRouter(options) {
|
|
|
1883
3100
|
return;
|
|
1884
3101
|
}
|
|
1885
3102
|
ctx.request.params = match.params;
|
|
1886
|
-
|
|
1887
|
-
await executeHandler(ctx, match.route, match.params);
|
|
1888
|
-
} catch (error) {
|
|
1889
|
-
handleRouteError(ctx, error, {
|
|
1890
|
-
detailed: process.env.NODE_ENV !== "production",
|
|
1891
|
-
log: true
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
3103
|
+
await executeHandler(ctx, match.route, match.params);
|
|
1894
3104
|
},
|
|
1895
3105
|
/**
|
|
1896
|
-
* Get all registered routes
|
|
3106
|
+
* Get all registered routes (using optimized registry)
|
|
1897
3107
|
*/
|
|
1898
3108
|
getRoutes() {
|
|
1899
|
-
return
|
|
3109
|
+
return getAllRoutesFromRegistry(registry);
|
|
1900
3110
|
},
|
|
1901
3111
|
/**
|
|
1902
3112
|
* Add a route programmatically
|
|
1903
3113
|
*/
|
|
1904
3114
|
addRoute(route) {
|
|
1905
|
-
|
|
3115
|
+
const changes = updateRoutesFromFile(registry, "programmatic", [route]);
|
|
3116
|
+
applyMatcherChanges(changes);
|
|
3117
|
+
},
|
|
3118
|
+
/**
|
|
3119
|
+
* Add multiple routes programmatically with batch processing
|
|
3120
|
+
*/
|
|
3121
|
+
addRoutes(routes) {
|
|
3122
|
+
const changes = updateRoutesFromFile(registry, "programmatic", routes);
|
|
3123
|
+
applyMatcherChanges(changes);
|
|
3124
|
+
return changes;
|
|
1906
3125
|
},
|
|
1907
3126
|
/**
|
|
1908
|
-
* Add a route directory (for plugins)
|
|
3127
|
+
* Add a route directory (for plugins) with optimized loading
|
|
1909
3128
|
*/
|
|
1910
3129
|
async addRouteDirectory(directory, options2 = {}) {
|
|
1911
3130
|
if (routeDirectories.has(directory)) {
|
|
@@ -1916,27 +3135,33 @@ function createRouter(options) {
|
|
|
1916
3135
|
if (initialized) {
|
|
1917
3136
|
await loadRoutesFromDirectory(directory, directory, options2.prefix);
|
|
1918
3137
|
if (routerOptions.watchMode) {
|
|
1919
|
-
|
|
3138
|
+
setupWatcherForNewDirectory(directory, options2.prefix);
|
|
1920
3139
|
}
|
|
1921
3140
|
}
|
|
1922
3141
|
},
|
|
1923
3142
|
/**
|
|
1924
|
-
* Get route conflicts
|
|
3143
|
+
* Get route conflicts (using registry)
|
|
1925
3144
|
*/
|
|
1926
3145
|
getRouteConflicts() {
|
|
1927
3146
|
const conflicts = [];
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
3147
|
+
return conflicts;
|
|
3148
|
+
},
|
|
3149
|
+
/**
|
|
3150
|
+
* Close watchers and cleanup (useful for testing)
|
|
3151
|
+
*/
|
|
3152
|
+
async close() {
|
|
3153
|
+
if (_watchers) {
|
|
3154
|
+
for (const watcher of _watchers.values()) {
|
|
3155
|
+
await watcher.close();
|
|
1931
3156
|
}
|
|
3157
|
+
_watchers.clear();
|
|
1932
3158
|
}
|
|
1933
|
-
return conflicts;
|
|
1934
3159
|
}
|
|
1935
3160
|
};
|
|
1936
3161
|
}
|
|
1937
3162
|
|
|
1938
3163
|
// src/server/create.ts
|
|
1939
|
-
var
|
|
3164
|
+
var DEFAULT_OPTIONS2 = {
|
|
1940
3165
|
port: 3e3,
|
|
1941
3166
|
host: "localhost",
|
|
1942
3167
|
routesDir: "./routes",
|
|
@@ -1947,7 +3172,7 @@ var DEFAULT_OPTIONS = {
|
|
|
1947
3172
|
plugins: []
|
|
1948
3173
|
};
|
|
1949
3174
|
function createServerOptions(options = {}) {
|
|
1950
|
-
const baseOptions = { ...
|
|
3175
|
+
const baseOptions = { ...DEFAULT_OPTIONS2 };
|
|
1951
3176
|
setRuntimeConfig({ routesDir: options.routesDir || baseOptions.routesDir });
|
|
1952
3177
|
return {
|
|
1953
3178
|
port: options.port ?? baseOptions.port,
|
|
@@ -2027,7 +3252,7 @@ function create3(options = {}) {
|
|
|
2027
3252
|
const { port, host, middleware, plugins } = validatedOptions;
|
|
2028
3253
|
const initialMiddleware = Array.isArray(middleware) ? [...middleware] : [];
|
|
2029
3254
|
const initialPlugins = Array.isArray(plugins) ? [...plugins] : [];
|
|
2030
|
-
const contextStorage2 = new
|
|
3255
|
+
const contextStorage2 = new import_node_async_hooks3.AsyncLocalStorage();
|
|
2031
3256
|
const router = createRouter({
|
|
2032
3257
|
routesDir: validatedOptions.routesDir,
|
|
2033
3258
|
watchMode: process.env.NODE_ENV === "development"
|
|
@@ -2067,7 +3292,106 @@ function create3(options = {}) {
|
|
|
2067
3292
|
return serverInstance;
|
|
2068
3293
|
}
|
|
2069
3294
|
|
|
3295
|
+
// ../blaize-types/src/index.ts
|
|
3296
|
+
init_errors();
|
|
3297
|
+
|
|
3298
|
+
// src/index.ts
|
|
3299
|
+
init_validation_error();
|
|
3300
|
+
|
|
3301
|
+
// src/errors/unauthorized-error.ts
|
|
3302
|
+
init_errors();
|
|
3303
|
+
init_correlation();
|
|
3304
|
+
var UnauthorizedError = class extends BlaizeError {
|
|
3305
|
+
/**
|
|
3306
|
+
* Creates a new UnauthorizedError instance
|
|
3307
|
+
*
|
|
3308
|
+
* @param title - Human-readable error message
|
|
3309
|
+
* @param details - Optional authentication context
|
|
3310
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3311
|
+
*/
|
|
3312
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3313
|
+
super(
|
|
3314
|
+
"UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
3315
|
+
title,
|
|
3316
|
+
401,
|
|
3317
|
+
// HTTP 401 Unauthorized
|
|
3318
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3319
|
+
details
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
|
|
3324
|
+
// src/errors/forbidden-error.ts
|
|
3325
|
+
init_errors();
|
|
3326
|
+
init_correlation();
|
|
3327
|
+
var ForbiddenError = class extends BlaizeError {
|
|
3328
|
+
/**
|
|
3329
|
+
* Creates a new ForbiddenError instance
|
|
3330
|
+
*
|
|
3331
|
+
* @param title - Human-readable error message
|
|
3332
|
+
* @param details - Optional permission context
|
|
3333
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3334
|
+
*/
|
|
3335
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3336
|
+
super(
|
|
3337
|
+
"FORBIDDEN" /* FORBIDDEN */,
|
|
3338
|
+
title,
|
|
3339
|
+
403,
|
|
3340
|
+
// HTTP 403 Forbidden
|
|
3341
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3342
|
+
details
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
|
|
3347
|
+
// src/errors/conflict-error.ts
|
|
3348
|
+
init_errors();
|
|
3349
|
+
init_correlation();
|
|
3350
|
+
var ConflictError = class extends BlaizeError {
|
|
3351
|
+
/**
|
|
3352
|
+
* Creates a new ConflictError instance
|
|
3353
|
+
*
|
|
3354
|
+
* @param title - Human-readable error message
|
|
3355
|
+
* @param details - Optional conflict context
|
|
3356
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3357
|
+
*/
|
|
3358
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3359
|
+
super(
|
|
3360
|
+
"CONFLICT" /* CONFLICT */,
|
|
3361
|
+
title,
|
|
3362
|
+
409,
|
|
3363
|
+
// HTTP 409 Conflict
|
|
3364
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3365
|
+
details
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
|
|
3370
|
+
// src/errors/rate-limit-error.ts
|
|
3371
|
+
init_errors();
|
|
3372
|
+
init_correlation();
|
|
3373
|
+
var RateLimitError = class extends BlaizeError {
|
|
3374
|
+
/**
|
|
3375
|
+
* Creates a new RateLimitError instance
|
|
3376
|
+
*
|
|
3377
|
+
* @param title - Human-readable error message
|
|
3378
|
+
* @param details - Optional rate limit context
|
|
3379
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3380
|
+
*/
|
|
3381
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3382
|
+
super(
|
|
3383
|
+
"RATE_LIMITED" /* RATE_LIMITED */,
|
|
3384
|
+
title,
|
|
3385
|
+
429,
|
|
3386
|
+
// HTTP 429 Too Many Requests
|
|
3387
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3388
|
+
details
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
};
|
|
3392
|
+
|
|
2070
3393
|
// src/index.ts
|
|
3394
|
+
init_internal_server_error();
|
|
2071
3395
|
var VERSION = "0.1.0";
|
|
2072
3396
|
var ServerAPI = { createServer: create3 };
|
|
2073
3397
|
var RouterAPI = {
|
|
@@ -2098,11 +3422,21 @@ var index_default = Blaize;
|
|
|
2098
3422
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2099
3423
|
0 && (module.exports = {
|
|
2100
3424
|
Blaize,
|
|
3425
|
+
BlaizeError,
|
|
3426
|
+
ConflictError,
|
|
3427
|
+
ErrorSeverity,
|
|
3428
|
+
ErrorType,
|
|
3429
|
+
ForbiddenError,
|
|
3430
|
+
InternalServerError,
|
|
2101
3431
|
MiddlewareAPI,
|
|
3432
|
+
NotFoundError,
|
|
2102
3433
|
PluginsAPI,
|
|
3434
|
+
RateLimitError,
|
|
2103
3435
|
RouterAPI,
|
|
2104
3436
|
ServerAPI,
|
|
3437
|
+
UnauthorizedError,
|
|
2105
3438
|
VERSION,
|
|
3439
|
+
ValidationError,
|
|
2106
3440
|
compose,
|
|
2107
3441
|
createDeleteRoute,
|
|
2108
3442
|
createGetRoute,
|
|
@@ -2113,6 +3447,7 @@ var index_default = Blaize;
|
|
|
2113
3447
|
createPlugin,
|
|
2114
3448
|
createPostRoute,
|
|
2115
3449
|
createPutRoute,
|
|
2116
|
-
createServer
|
|
3450
|
+
createServer,
|
|
3451
|
+
isBodyParseError
|
|
2117
3452
|
});
|
|
2118
3453
|
//# sourceMappingURL=index.cjs.map
|