blaizejs 0.2.3 → 0.3.1
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-3A5J5MKL.js +38 -0
- package/dist/chunk-3A5J5MKL.js.map +1 -0
- package/dist/chunk-SF7ZGOEK.js +148 -0
- package/dist/chunk-SF7ZGOEK.js.map +1 -0
- package/dist/chunk-ZZEQFU5V.js +38 -0
- package/dist/chunk-ZZEQFU5V.js.map +1 -0
- package/dist/index.cjs +1075 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1283 -239
- package/dist/index.d.ts +1283 -239
- package/dist/index.js +800 -152
- package/dist/index.js.map +1 -1
- package/dist/internal-server-error-PKVC3ZEU.js +16 -0
- package/dist/internal-server-error-PKVC3ZEU.js.map +1 -0
- package/dist/payload-too-large-error-WZMDORKR.js +29 -0
- package/dist/payload-too-large-error-WZMDORKR.js.map +1 -0
- package/dist/unsupported-media-type-error-VUXOJ72O.js +29 -0
- package/dist/unsupported-media-type-error-VUXOJ72O.js.map +1 -0
- package/dist/validation-error-WZFF75S7.js +16 -0
- package/dist/validation-error-WZFF75S7.js.map +1 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* blaizejs v0.
|
|
2
|
+
* blaizejs v0.3.1
|
|
3
3
|
* A blazing-fast, TypeScript-first Node.js framework with HTTP/2 support, file-based routing, powerful middleware system, and end-to-end type safety for building modern APIs.
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) 2025 BlaizeJS Contributors
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
9
|
+
import {
|
|
10
|
+
InternalServerError
|
|
11
|
+
} from "./chunk-ZZEQFU5V.js";
|
|
12
|
+
import {
|
|
13
|
+
ValidationError
|
|
14
|
+
} from "./chunk-3A5J5MKL.js";
|
|
15
|
+
import {
|
|
16
|
+
BlaizeError,
|
|
17
|
+
ErrorSeverity,
|
|
18
|
+
ErrorType,
|
|
19
|
+
__require,
|
|
20
|
+
generateCorrelationId,
|
|
21
|
+
getCurrentCorrelationId,
|
|
22
|
+
isBodyParseError
|
|
23
|
+
} from "./chunk-SF7ZGOEK.js";
|
|
15
24
|
|
|
16
25
|
// src/middleware/execute.ts
|
|
17
26
|
function execute(middleware, ctx, next) {
|
|
@@ -207,6 +216,7 @@ var createGetRoute = (config2) => {
|
|
|
207
216
|
const path6 = getRoutePath();
|
|
208
217
|
return {
|
|
209
218
|
GET: config2,
|
|
219
|
+
// Let TypeScript infer the proper types
|
|
210
220
|
path: path6
|
|
211
221
|
};
|
|
212
222
|
};
|
|
@@ -215,6 +225,7 @@ var createPostRoute = (config2) => {
|
|
|
215
225
|
const path6 = getRoutePath();
|
|
216
226
|
return {
|
|
217
227
|
POST: config2,
|
|
228
|
+
// Let TypeScript infer the proper types
|
|
218
229
|
path: path6
|
|
219
230
|
};
|
|
220
231
|
};
|
|
@@ -223,6 +234,7 @@ var createPutRoute = (config2) => {
|
|
|
223
234
|
const path6 = getRoutePath();
|
|
224
235
|
return {
|
|
225
236
|
PUT: config2,
|
|
237
|
+
// Let TypeScript infer the proper types
|
|
226
238
|
path: path6
|
|
227
239
|
};
|
|
228
240
|
};
|
|
@@ -231,6 +243,7 @@ var createDeleteRoute = (config2) => {
|
|
|
231
243
|
const path6 = getRoutePath();
|
|
232
244
|
return {
|
|
233
245
|
DELETE: config2,
|
|
246
|
+
// Let TypeScript infer the proper types
|
|
234
247
|
path: path6
|
|
235
248
|
};
|
|
236
249
|
};
|
|
@@ -239,6 +252,7 @@ var createPatchRoute = (config2) => {
|
|
|
239
252
|
const path6 = getRoutePath();
|
|
240
253
|
return {
|
|
241
254
|
PATCH: config2,
|
|
255
|
+
// Let TypeScript infer the proper types
|
|
242
256
|
path: path6
|
|
243
257
|
};
|
|
244
258
|
};
|
|
@@ -247,6 +261,7 @@ var createHeadRoute = (config2) => {
|
|
|
247
261
|
const path6 = getRoutePath();
|
|
248
262
|
return {
|
|
249
263
|
HEAD: config2,
|
|
264
|
+
// Let TypeScript infer the proper types
|
|
250
265
|
path: path6
|
|
251
266
|
};
|
|
252
267
|
};
|
|
@@ -255,6 +270,7 @@ var createOptionsRoute = (config2) => {
|
|
|
255
270
|
const path6 = getRoutePath();
|
|
256
271
|
return {
|
|
257
272
|
OPTIONS: config2,
|
|
273
|
+
// Let TypeScript infer the proper types
|
|
258
274
|
path: path6
|
|
259
275
|
};
|
|
260
276
|
};
|
|
@@ -391,8 +407,474 @@ function runWithContext(context, callback) {
|
|
|
391
407
|
return contextStorage.run(context, callback);
|
|
392
408
|
}
|
|
393
409
|
|
|
410
|
+
// src/upload/multipart-parser.ts
|
|
411
|
+
import { randomUUID } from "node:crypto";
|
|
412
|
+
import { createWriteStream } from "node:fs";
|
|
413
|
+
import { tmpdir } from "node:os";
|
|
414
|
+
import { join as join2 } from "node:path";
|
|
415
|
+
import { Readable } from "node:stream";
|
|
416
|
+
|
|
417
|
+
// src/upload/utils.ts
|
|
418
|
+
var BOUNDARY_REGEX = /boundary=([^;]+)/i;
|
|
419
|
+
var CONTENT_DISPOSITION_REGEX = /Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;[\s\r\n]*filename="([^"]*)")?/i;
|
|
420
|
+
var CONTENT_TYPE_REGEX = /Content-Type:\s*([^\r\n]+)/i;
|
|
421
|
+
var MULTIPART_REGEX = /multipart\/form-data/i;
|
|
422
|
+
function extractBoundary(contentType) {
|
|
423
|
+
const match = contentType.match(BOUNDARY_REGEX);
|
|
424
|
+
if (!match || !match[1]) return null;
|
|
425
|
+
let boundary = match[1].trim();
|
|
426
|
+
if (boundary.startsWith('"') && boundary.endsWith('"')) {
|
|
427
|
+
boundary = boundary.slice(1, -1);
|
|
428
|
+
}
|
|
429
|
+
return boundary || null;
|
|
430
|
+
}
|
|
431
|
+
function parseContentDisposition(headers) {
|
|
432
|
+
const match = headers.match(CONTENT_DISPOSITION_REGEX);
|
|
433
|
+
if (!match || !match[1]) return null;
|
|
434
|
+
return {
|
|
435
|
+
name: match[1],
|
|
436
|
+
filename: match[2] !== void 0 ? match[2] : void 0
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function parseContentType(headers) {
|
|
440
|
+
const match = headers.match(CONTENT_TYPE_REGEX);
|
|
441
|
+
return match && match[1]?.trim() ? match[1].trim() : "application/octet-stream";
|
|
442
|
+
}
|
|
443
|
+
function isMultipartContent(contentType) {
|
|
444
|
+
return MULTIPART_REGEX.test(contentType);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/upload/multipart-parser.ts
|
|
448
|
+
var DEFAULT_OPTIONS = {
|
|
449
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
450
|
+
// 10MB
|
|
451
|
+
maxFiles: 10,
|
|
452
|
+
maxFieldSize: 1 * 1024 * 1024,
|
|
453
|
+
// 1MB
|
|
454
|
+
allowedMimeTypes: [],
|
|
455
|
+
allowedExtensions: [],
|
|
456
|
+
strategy: "stream",
|
|
457
|
+
tempDir: tmpdir(),
|
|
458
|
+
computeHash: false
|
|
459
|
+
};
|
|
460
|
+
function createParserState(boundary, options = {}) {
|
|
461
|
+
return {
|
|
462
|
+
boundary: Buffer.from(`--${boundary}`),
|
|
463
|
+
options: { ...DEFAULT_OPTIONS, ...options },
|
|
464
|
+
fields: /* @__PURE__ */ new Map(),
|
|
465
|
+
files: /* @__PURE__ */ new Map(),
|
|
466
|
+
buffer: Buffer.alloc(0),
|
|
467
|
+
stage: "boundary",
|
|
468
|
+
currentHeaders: "",
|
|
469
|
+
currentField: null,
|
|
470
|
+
currentFilename: void 0,
|
|
471
|
+
currentMimetype: "application/octet-stream",
|
|
472
|
+
currentContentLength: 0,
|
|
473
|
+
fileCount: 0,
|
|
474
|
+
fieldCount: 0,
|
|
475
|
+
currentBufferChunks: [],
|
|
476
|
+
currentStream: null,
|
|
477
|
+
currentTempPath: null,
|
|
478
|
+
currentWriteStream: null,
|
|
479
|
+
streamController: null,
|
|
480
|
+
cleanupTasks: [],
|
|
481
|
+
// Track validation state
|
|
482
|
+
hasFoundValidBoundary: false,
|
|
483
|
+
hasProcessedAnyPart: false,
|
|
484
|
+
isFinished: false
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
async function processChunk(state, chunk) {
|
|
488
|
+
const newBuffer = Buffer.concat([state.buffer, chunk]);
|
|
489
|
+
let currentState = { ...state, buffer: newBuffer };
|
|
490
|
+
while (currentState.buffer.length > 0 && !currentState.isFinished) {
|
|
491
|
+
const nextState = await processCurrentStage(currentState);
|
|
492
|
+
if (nextState === currentState) break;
|
|
493
|
+
currentState = nextState;
|
|
494
|
+
}
|
|
495
|
+
return currentState;
|
|
496
|
+
}
|
|
497
|
+
async function processCurrentStage(state) {
|
|
498
|
+
switch (state.stage) {
|
|
499
|
+
case "boundary":
|
|
500
|
+
return processBoundary(state);
|
|
501
|
+
case "headers":
|
|
502
|
+
return processHeaders(state);
|
|
503
|
+
case "content":
|
|
504
|
+
return processContent(state);
|
|
505
|
+
default: {
|
|
506
|
+
const { InternalServerError: InternalServerError2 } = await import("./internal-server-error-PKVC3ZEU.js");
|
|
507
|
+
throw new InternalServerError2(`Invalid parser stage`, {
|
|
508
|
+
operation: state.stage
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function processBoundary(state) {
|
|
514
|
+
const boundaryIndex = state.buffer.indexOf(state.boundary);
|
|
515
|
+
if (boundaryIndex === -1) return state;
|
|
516
|
+
const hasFoundValidBoundary = true;
|
|
517
|
+
let buffer = state.buffer.subarray(boundaryIndex + state.boundary.length);
|
|
518
|
+
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("--"))) {
|
|
519
|
+
return {
|
|
520
|
+
...state,
|
|
521
|
+
buffer,
|
|
522
|
+
hasFoundValidBoundary,
|
|
523
|
+
isFinished: true,
|
|
524
|
+
stage: "boundary"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (buffer.length >= 2 && buffer.subarray(0, 2).equals(Buffer.from("\r\n"))) {
|
|
528
|
+
buffer = buffer.subarray(2);
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
...state,
|
|
532
|
+
buffer,
|
|
533
|
+
hasFoundValidBoundary,
|
|
534
|
+
stage: "headers",
|
|
535
|
+
currentHeaders: ""
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function processHeaders(state) {
|
|
539
|
+
const headerEnd = state.buffer.indexOf("\r\n\r\n");
|
|
540
|
+
if (headerEnd === -1) return state;
|
|
541
|
+
const headers = state.buffer.subarray(0, headerEnd).toString("utf8");
|
|
542
|
+
const buffer = state.buffer.subarray(headerEnd + 4);
|
|
543
|
+
const disposition = parseContentDisposition(headers);
|
|
544
|
+
if (!disposition) {
|
|
545
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
546
|
+
throw new ValidationError2("Missing or invalid Content-Disposition header");
|
|
547
|
+
}
|
|
548
|
+
const mimetype = parseContentType(headers);
|
|
549
|
+
const isFile = disposition.filename !== void 0;
|
|
550
|
+
if (isFile && state.fileCount >= state.options.maxFiles) {
|
|
551
|
+
const { PayloadTooLargeError } = await import("./payload-too-large-error-WZMDORKR.js");
|
|
552
|
+
throw new PayloadTooLargeError("Too many files in upload", {
|
|
553
|
+
fileCount: state.fileCount + 1,
|
|
554
|
+
maxFiles: state.options.maxFiles,
|
|
555
|
+
filename: disposition.filename
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (isFile && state.options.allowedMimeTypes.length > 0 && !state.options.allowedMimeTypes.includes(mimetype)) {
|
|
559
|
+
const { UnsupportedMediaTypeError } = await import("./unsupported-media-type-error-VUXOJ72O.js");
|
|
560
|
+
throw new UnsupportedMediaTypeError("File type not allowed", {
|
|
561
|
+
receivedMimeType: mimetype,
|
|
562
|
+
allowedMimeTypes: state.options.allowedMimeTypes,
|
|
563
|
+
filename: disposition.filename
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
...state,
|
|
568
|
+
buffer,
|
|
569
|
+
stage: "content",
|
|
570
|
+
currentHeaders: headers,
|
|
571
|
+
currentField: disposition.name,
|
|
572
|
+
currentFilename: disposition.filename,
|
|
573
|
+
currentMimetype: mimetype,
|
|
574
|
+
currentContentLength: 0,
|
|
575
|
+
fileCount: isFile ? state.fileCount + 1 : state.fileCount,
|
|
576
|
+
fieldCount: isFile ? state.fieldCount : state.fieldCount + 1,
|
|
577
|
+
currentBufferChunks: []
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
async function processContent(state) {
|
|
581
|
+
const nextBoundaryIndex = state.buffer.indexOf(state.boundary);
|
|
582
|
+
let contentChunk;
|
|
583
|
+
let isComplete = false;
|
|
584
|
+
let buffer = state.buffer;
|
|
585
|
+
if (nextBoundaryIndex === -1) {
|
|
586
|
+
const safeLength = Math.max(0, state.buffer.length - state.boundary.length);
|
|
587
|
+
if (safeLength === 0) return state;
|
|
588
|
+
contentChunk = state.buffer.subarray(0, safeLength);
|
|
589
|
+
buffer = state.buffer.subarray(safeLength);
|
|
590
|
+
} else {
|
|
591
|
+
const contentEnd = Math.max(0, nextBoundaryIndex - 2);
|
|
592
|
+
contentChunk = state.buffer.subarray(0, contentEnd);
|
|
593
|
+
buffer = state.buffer.subarray(nextBoundaryIndex);
|
|
594
|
+
isComplete = true;
|
|
595
|
+
}
|
|
596
|
+
let updatedState = { ...state, buffer };
|
|
597
|
+
if (contentChunk.length > 0) {
|
|
598
|
+
updatedState = await processContentChunk(updatedState, contentChunk);
|
|
599
|
+
}
|
|
600
|
+
if (isComplete) {
|
|
601
|
+
updatedState = await finalizeCurrentPart(updatedState);
|
|
602
|
+
updatedState = {
|
|
603
|
+
...updatedState,
|
|
604
|
+
stage: "boundary",
|
|
605
|
+
hasProcessedAnyPart: true
|
|
606
|
+
// Mark that we've processed at least one part
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return updatedState;
|
|
610
|
+
}
|
|
611
|
+
async function processContentChunk(state, chunk) {
|
|
612
|
+
const newContentLength = state.currentContentLength + chunk.length;
|
|
613
|
+
const maxSize = state.currentFilename !== void 0 ? state.options.maxFileSize : state.options.maxFieldSize;
|
|
614
|
+
if (newContentLength > maxSize) {
|
|
615
|
+
const isFile = state.currentFilename !== void 0;
|
|
616
|
+
const { PayloadTooLargeError } = await import("./payload-too-large-error-WZMDORKR.js");
|
|
617
|
+
const payloadErrorDetals = state.currentField ? {
|
|
618
|
+
contentType: isFile ? "file" : "field",
|
|
619
|
+
currentSize: newContentLength,
|
|
620
|
+
maxSize,
|
|
621
|
+
field: state.currentField,
|
|
622
|
+
filename: state.currentFilename
|
|
623
|
+
} : {
|
|
624
|
+
contentType: isFile ? "file" : "field",
|
|
625
|
+
currentSize: newContentLength,
|
|
626
|
+
maxSize,
|
|
627
|
+
filename: state.currentFilename
|
|
628
|
+
};
|
|
629
|
+
throw new PayloadTooLargeError(
|
|
630
|
+
`${isFile ? "File" : "Field"} size exceeds limit`,
|
|
631
|
+
payloadErrorDetals
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (state.currentFilename !== void 0) {
|
|
635
|
+
return processFileChunk(state, chunk, newContentLength);
|
|
636
|
+
} else {
|
|
637
|
+
return {
|
|
638
|
+
...state,
|
|
639
|
+
currentContentLength: newContentLength,
|
|
640
|
+
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function processFileChunk(state, chunk, newContentLength) {
|
|
645
|
+
switch (state.options.strategy) {
|
|
646
|
+
case "memory":
|
|
647
|
+
return {
|
|
648
|
+
...state,
|
|
649
|
+
currentContentLength: newContentLength,
|
|
650
|
+
currentBufferChunks: [...state.currentBufferChunks, chunk]
|
|
651
|
+
};
|
|
652
|
+
case "stream":
|
|
653
|
+
if (state.streamController) {
|
|
654
|
+
state.streamController.enqueue(chunk);
|
|
655
|
+
}
|
|
656
|
+
return { ...state, currentContentLength: newContentLength };
|
|
657
|
+
case "temp":
|
|
658
|
+
if (state.currentWriteStream) {
|
|
659
|
+
await writeToStream(state.currentWriteStream, chunk);
|
|
660
|
+
}
|
|
661
|
+
return { ...state, currentContentLength: newContentLength };
|
|
662
|
+
default: {
|
|
663
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
664
|
+
throw new ValidationError2(`Invalid parsing strategy`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function initializeFileProcessing(state) {
|
|
669
|
+
if (state.currentFilename === void 0) return state;
|
|
670
|
+
switch (state.options.strategy) {
|
|
671
|
+
case "memory":
|
|
672
|
+
return { ...state, currentBufferChunks: [] };
|
|
673
|
+
case "stream": {
|
|
674
|
+
let streamController = null;
|
|
675
|
+
const stream = new ReadableStream({
|
|
676
|
+
start: (controller) => {
|
|
677
|
+
streamController = controller;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
return {
|
|
681
|
+
...state,
|
|
682
|
+
currentStream: stream,
|
|
683
|
+
// Type cast for Node.js compatibility
|
|
684
|
+
streamController
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
case "temp": {
|
|
688
|
+
const tempPath = join2(state.options.tempDir, `upload-${randomUUID()}`);
|
|
689
|
+
const writeStream = createWriteStream(tempPath);
|
|
690
|
+
const cleanupTask = async () => {
|
|
691
|
+
try {
|
|
692
|
+
const { unlink } = await import("node:fs/promises");
|
|
693
|
+
await unlink(tempPath);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
console.warn(`Failed to cleanup temp file: ${tempPath}`, error);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
return {
|
|
699
|
+
...state,
|
|
700
|
+
currentTempPath: tempPath,
|
|
701
|
+
currentWriteStream: writeStream,
|
|
702
|
+
cleanupTasks: [...state.cleanupTasks, cleanupTask]
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
default: {
|
|
706
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
707
|
+
throw new ValidationError2(`Invalid file processing strategy`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async function finalizeCurrentPart(state) {
|
|
712
|
+
if (!state.currentField) return resetCurrentPart(state);
|
|
713
|
+
if (state.currentFilename !== void 0) {
|
|
714
|
+
return finalizeFile(state);
|
|
715
|
+
} else {
|
|
716
|
+
return finalizeField(state);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function finalizeFile(state) {
|
|
720
|
+
if (!state.currentField || state.currentFilename === void 0) {
|
|
721
|
+
return resetCurrentPart(state);
|
|
722
|
+
}
|
|
723
|
+
let stream;
|
|
724
|
+
let buffer;
|
|
725
|
+
let tempPath;
|
|
726
|
+
switch (state.options.strategy) {
|
|
727
|
+
case "memory":
|
|
728
|
+
buffer = Buffer.concat(state.currentBufferChunks);
|
|
729
|
+
stream = Readable.from(buffer);
|
|
730
|
+
break;
|
|
731
|
+
case "stream":
|
|
732
|
+
if (state.streamController) {
|
|
733
|
+
state.streamController.close();
|
|
734
|
+
}
|
|
735
|
+
stream = state.currentStream;
|
|
736
|
+
break;
|
|
737
|
+
case "temp":
|
|
738
|
+
if (state.currentWriteStream) {
|
|
739
|
+
await closeStream(state.currentWriteStream);
|
|
740
|
+
}
|
|
741
|
+
tempPath = state.currentTempPath;
|
|
742
|
+
stream = Readable.from(Buffer.alloc(0));
|
|
743
|
+
break;
|
|
744
|
+
default: {
|
|
745
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
746
|
+
throw new ValidationError2(`Invalid file finalization strategy`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const file = {
|
|
750
|
+
filename: state.currentFilename,
|
|
751
|
+
fieldname: state.currentField,
|
|
752
|
+
mimetype: state.currentMimetype,
|
|
753
|
+
size: state.currentContentLength,
|
|
754
|
+
stream,
|
|
755
|
+
buffer,
|
|
756
|
+
tempPath
|
|
757
|
+
};
|
|
758
|
+
const updatedFiles = addToCollection(state.files, state.currentField, file);
|
|
759
|
+
return {
|
|
760
|
+
...resetCurrentPart(state),
|
|
761
|
+
files: updatedFiles
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function finalizeField(state) {
|
|
765
|
+
if (!state.currentField) return resetCurrentPart(state);
|
|
766
|
+
const value = Buffer.concat(state.currentBufferChunks).toString("utf8");
|
|
767
|
+
const updatedFields = addToCollection(state.fields, state.currentField, value);
|
|
768
|
+
return {
|
|
769
|
+
...resetCurrentPart(state),
|
|
770
|
+
fields: updatedFields
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function resetCurrentPart(state) {
|
|
774
|
+
return {
|
|
775
|
+
...state,
|
|
776
|
+
currentField: null,
|
|
777
|
+
currentFilename: void 0,
|
|
778
|
+
currentContentLength: 0,
|
|
779
|
+
currentBufferChunks: [],
|
|
780
|
+
currentStream: null,
|
|
781
|
+
streamController: null,
|
|
782
|
+
currentTempPath: null,
|
|
783
|
+
currentWriteStream: null
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function addToCollection(collection, key, value) {
|
|
787
|
+
const newCollection = new Map(collection);
|
|
788
|
+
const existing = newCollection.get(key) || [];
|
|
789
|
+
newCollection.set(key, [...existing, value]);
|
|
790
|
+
return newCollection;
|
|
791
|
+
}
|
|
792
|
+
async function finalize(state) {
|
|
793
|
+
if (!state.hasFoundValidBoundary) {
|
|
794
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
795
|
+
throw new ValidationError2("No valid multipart boundary found");
|
|
796
|
+
}
|
|
797
|
+
if (state.hasFoundValidBoundary && !state.hasProcessedAnyPart) {
|
|
798
|
+
const { ValidationError: ValidationError2 } = await import("./validation-error-WZFF75S7.js");
|
|
799
|
+
throw new ValidationError2("Empty multipart request");
|
|
800
|
+
}
|
|
801
|
+
const fields = {};
|
|
802
|
+
for (const [key, values] of state.fields.entries()) {
|
|
803
|
+
fields[key] = values.length === 1 ? values[0] : values;
|
|
804
|
+
}
|
|
805
|
+
const files = {};
|
|
806
|
+
for (const [key, fileList] of state.files.entries()) {
|
|
807
|
+
files[key] = fileList.length === 1 ? fileList[0] : fileList;
|
|
808
|
+
}
|
|
809
|
+
return { fields, files };
|
|
810
|
+
}
|
|
811
|
+
async function cleanup(state) {
|
|
812
|
+
await Promise.allSettled(state.cleanupTasks.map((task) => task()));
|
|
813
|
+
if (state.streamController) {
|
|
814
|
+
state.streamController.close();
|
|
815
|
+
}
|
|
816
|
+
if (state.currentWriteStream) {
|
|
817
|
+
await closeStream(state.currentWriteStream);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function writeToStream(stream, chunk) {
|
|
821
|
+
return new Promise((resolve3, reject) => {
|
|
822
|
+
stream.write(chunk, (error) => {
|
|
823
|
+
if (error) reject(error);
|
|
824
|
+
else resolve3();
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
async function closeStream(stream) {
|
|
829
|
+
return new Promise((resolve3) => {
|
|
830
|
+
stream.end(() => resolve3());
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
async function parseMultipartRequest(request, options = {}) {
|
|
834
|
+
const contentType = request.headers["content-type"] || "";
|
|
835
|
+
const boundary = extractBoundary(contentType);
|
|
836
|
+
if (!boundary) {
|
|
837
|
+
const { UnsupportedMediaTypeError } = await import("./unsupported-media-type-error-VUXOJ72O.js");
|
|
838
|
+
throw new UnsupportedMediaTypeError("Missing boundary in multipart content-type", {
|
|
839
|
+
receivedContentType: contentType,
|
|
840
|
+
expectedFormat: "multipart/form-data; boundary=..."
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
let state = createParserState(boundary, options);
|
|
844
|
+
if (state.currentFilename !== void 0) {
|
|
845
|
+
state = await initializeFileProcessing(state);
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
for await (const chunk of request) {
|
|
849
|
+
state = await processChunk(state, chunk);
|
|
850
|
+
}
|
|
851
|
+
return finalize(state);
|
|
852
|
+
} finally {
|
|
853
|
+
await cleanup(state);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
394
857
|
// src/context/create.ts
|
|
395
858
|
var CONTENT_TYPE_HEADER = "Content-Type";
|
|
859
|
+
var DEFAULT_BODY_LIMITS = {
|
|
860
|
+
json: 512 * 1024,
|
|
861
|
+
// 512KB - Most APIs should be much smaller
|
|
862
|
+
form: 1024 * 1024,
|
|
863
|
+
// 1MB - Reasonable for form submissions
|
|
864
|
+
text: 5 * 1024 * 1024,
|
|
865
|
+
// 5MB - Documents, logs, code files
|
|
866
|
+
multipart: {
|
|
867
|
+
maxFileSize: 50 * 1024 * 1024,
|
|
868
|
+
// 50MB per file
|
|
869
|
+
maxTotalSize: 100 * 1024 * 1024,
|
|
870
|
+
// 100MB total request
|
|
871
|
+
maxFiles: 10,
|
|
872
|
+
maxFieldSize: 1024 * 1024
|
|
873
|
+
// 1MB for form fields
|
|
874
|
+
},
|
|
875
|
+
raw: 10 * 1024 * 1024
|
|
876
|
+
// 10MB for unknown content types
|
|
877
|
+
};
|
|
396
878
|
function parseRequestUrl(req) {
|
|
397
879
|
const originalUrl = req.url || "/";
|
|
398
880
|
const host = req.headers.host || "localhost";
|
|
@@ -457,7 +939,7 @@ async function createContext(req, res, options = {}) {
|
|
|
457
939
|
};
|
|
458
940
|
ctx.response = createResponseObject(res, responseState, ctx);
|
|
459
941
|
if (options.parseBody) {
|
|
460
|
-
await parseBodyIfNeeded(req, ctx);
|
|
942
|
+
await parseBodyIfNeeded(req, ctx, options);
|
|
461
943
|
}
|
|
462
944
|
return ctx;
|
|
463
945
|
}
|
|
@@ -633,34 +1115,60 @@ function createStreamResponder(res, responseState) {
|
|
|
633
1115
|
});
|
|
634
1116
|
};
|
|
635
1117
|
}
|
|
636
|
-
async function parseBodyIfNeeded(req, ctx) {
|
|
1118
|
+
async function parseBodyIfNeeded(req, ctx, options = {}) {
|
|
637
1119
|
if (shouldSkipParsing(req.method)) {
|
|
638
1120
|
return;
|
|
639
1121
|
}
|
|
640
1122
|
const contentType = req.headers["content-type"] || "";
|
|
641
1123
|
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
|
642
|
-
if (contentLength === 0
|
|
1124
|
+
if (contentLength === 0) {
|
|
643
1125
|
return;
|
|
644
1126
|
}
|
|
1127
|
+
const limits = {
|
|
1128
|
+
json: options.bodyLimits?.json ?? DEFAULT_BODY_LIMITS.json,
|
|
1129
|
+
form: options.bodyLimits?.form ?? DEFAULT_BODY_LIMITS.form,
|
|
1130
|
+
text: options.bodyLimits?.text ?? DEFAULT_BODY_LIMITS.text,
|
|
1131
|
+
raw: options.bodyLimits?.raw ?? DEFAULT_BODY_LIMITS.raw,
|
|
1132
|
+
multipart: {
|
|
1133
|
+
maxFileSize: options.bodyLimits?.multipart?.maxFileSize ?? DEFAULT_BODY_LIMITS.multipart.maxFileSize,
|
|
1134
|
+
maxFiles: options.bodyLimits?.multipart?.maxFiles ?? DEFAULT_BODY_LIMITS.multipart.maxFiles,
|
|
1135
|
+
maxFieldSize: options.bodyLimits?.multipart?.maxFieldSize ?? DEFAULT_BODY_LIMITS.multipart.maxFieldSize,
|
|
1136
|
+
maxTotalSize: options.bodyLimits?.multipart?.maxTotalSize ?? DEFAULT_BODY_LIMITS.multipart.maxTotalSize
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
645
1139
|
try {
|
|
646
|
-
|
|
1140
|
+
if (contentType.includes("application/json")) {
|
|
1141
|
+
if (contentLength > limits.json) {
|
|
1142
|
+
throw new Error(`JSON body too large: ${contentLength} > ${limits.json} bytes`);
|
|
1143
|
+
}
|
|
1144
|
+
await parseJsonBody(req, ctx);
|
|
1145
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1146
|
+
if (contentLength > limits.form) {
|
|
1147
|
+
throw new Error(`Form body too large: ${contentLength} > ${limits.form} bytes`);
|
|
1148
|
+
}
|
|
1149
|
+
await parseFormUrlEncodedBody(req, ctx);
|
|
1150
|
+
} else if (contentType.includes("text/")) {
|
|
1151
|
+
if (contentLength > limits.text) {
|
|
1152
|
+
throw new Error(`Text body too large: ${contentLength} > ${limits.text} bytes`);
|
|
1153
|
+
}
|
|
1154
|
+
await parseTextBody(req, ctx);
|
|
1155
|
+
} else if (isMultipartContent(contentType)) {
|
|
1156
|
+
await parseMultipartBody(req, ctx, limits.multipart);
|
|
1157
|
+
} else {
|
|
1158
|
+
if (contentLength > limits.raw) {
|
|
1159
|
+
throw new Error(`Request body too large: ${contentLength} > ${limits.raw} bytes`);
|
|
1160
|
+
}
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
647
1163
|
} catch (error) {
|
|
648
|
-
|
|
1164
|
+
const errorType = contentType.includes("multipart") ? "multipart_parse_error" : "body_read_error";
|
|
1165
|
+
setBodyError(ctx, errorType, "Error reading request body", error);
|
|
649
1166
|
}
|
|
650
1167
|
}
|
|
651
1168
|
function shouldSkipParsing(method) {
|
|
652
1169
|
const skipMethods = ["GET", "HEAD", "OPTIONS"];
|
|
653
1170
|
return skipMethods.includes(method || "GET");
|
|
654
1171
|
}
|
|
655
|
-
async function parseBodyByContentType(req, ctx, contentType) {
|
|
656
|
-
if (contentType.includes("application/json")) {
|
|
657
|
-
await parseJsonBody(req, ctx);
|
|
658
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
659
|
-
await parseFormUrlEncodedBody(req, ctx);
|
|
660
|
-
} else if (contentType.includes("text/")) {
|
|
661
|
-
await parseTextBody(req, ctx);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
1172
|
async function parseJsonBody(req, ctx) {
|
|
665
1173
|
const body = await readRequestBody(req);
|
|
666
1174
|
if (!body) {
|
|
@@ -712,8 +1220,27 @@ async function parseTextBody(req, ctx) {
|
|
|
712
1220
|
ctx.request.body = body;
|
|
713
1221
|
}
|
|
714
1222
|
}
|
|
1223
|
+
async function parseMultipartBody(req, ctx, multipartLimits) {
|
|
1224
|
+
try {
|
|
1225
|
+
const limits = multipartLimits || DEFAULT_BODY_LIMITS.multipart;
|
|
1226
|
+
const multipartData = await parseMultipartRequest(req, {
|
|
1227
|
+
strategy: "stream",
|
|
1228
|
+
maxFileSize: limits.maxFileSize,
|
|
1229
|
+
maxFiles: limits.maxFiles,
|
|
1230
|
+
maxFieldSize: limits.maxFieldSize
|
|
1231
|
+
// Could add total size validation here
|
|
1232
|
+
});
|
|
1233
|
+
ctx.request.multipart = multipartData;
|
|
1234
|
+
ctx.request.files = multipartData.files;
|
|
1235
|
+
ctx.request.body = multipartData.fields;
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
ctx.request.body = null;
|
|
1238
|
+
setBodyError(ctx, "multipart_parse_error", "Failed to parse multipart data", error);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
715
1241
|
function setBodyError(ctx, type, message, error) {
|
|
716
|
-
|
|
1242
|
+
const bodyError = { type, message, error };
|
|
1243
|
+
ctx.state._bodyError = bodyError;
|
|
717
1244
|
}
|
|
718
1245
|
async function readRequestBody(req) {
|
|
719
1246
|
return new Promise((resolve3, reject) => {
|
|
@@ -730,6 +1257,102 @@ async function readRequestBody(req) {
|
|
|
730
1257
|
});
|
|
731
1258
|
}
|
|
732
1259
|
|
|
1260
|
+
// src/errors/not-found-error.ts
|
|
1261
|
+
var NotFoundError = class extends BlaizeError {
|
|
1262
|
+
/**
|
|
1263
|
+
* Creates a new NotFoundError instance
|
|
1264
|
+
*
|
|
1265
|
+
* @param title - Human-readable error message
|
|
1266
|
+
* @param details - Optional context about the missing resource
|
|
1267
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
1268
|
+
*/
|
|
1269
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
1270
|
+
super(
|
|
1271
|
+
"NOT_FOUND" /* NOT_FOUND */,
|
|
1272
|
+
title,
|
|
1273
|
+
404,
|
|
1274
|
+
// HTTP 404 Not Found
|
|
1275
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
1276
|
+
details
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
// src/errors/boundary.ts
|
|
1282
|
+
function isHandledError(error) {
|
|
1283
|
+
return error instanceof BlaizeError;
|
|
1284
|
+
}
|
|
1285
|
+
function formatErrorResponse(error) {
|
|
1286
|
+
if (isHandledError(error)) {
|
|
1287
|
+
return {
|
|
1288
|
+
type: error.type,
|
|
1289
|
+
title: error.title,
|
|
1290
|
+
status: error.status,
|
|
1291
|
+
correlationId: error.correlationId,
|
|
1292
|
+
timestamp: error.timestamp.toISOString(),
|
|
1293
|
+
details: error.details
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
const correlationId = generateCorrelationId();
|
|
1297
|
+
let originalMessage;
|
|
1298
|
+
if (error instanceof Error) {
|
|
1299
|
+
originalMessage = error.message;
|
|
1300
|
+
} else if (error === null || error === void 0) {
|
|
1301
|
+
originalMessage = "Unknown error occurred";
|
|
1302
|
+
} else {
|
|
1303
|
+
originalMessage = String(error);
|
|
1304
|
+
}
|
|
1305
|
+
const wrappedError = new InternalServerError(
|
|
1306
|
+
"Internal Server Error",
|
|
1307
|
+
{ originalMessage },
|
|
1308
|
+
correlationId
|
|
1309
|
+
);
|
|
1310
|
+
return {
|
|
1311
|
+
type: wrappedError.type,
|
|
1312
|
+
title: wrappedError.title,
|
|
1313
|
+
status: wrappedError.status,
|
|
1314
|
+
correlationId: wrappedError.correlationId,
|
|
1315
|
+
timestamp: wrappedError.timestamp.toISOString(),
|
|
1316
|
+
details: wrappedError.details
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function extractOrGenerateCorrelationId(headerGetter) {
|
|
1320
|
+
return headerGetter("x-correlation-id") ?? generateCorrelationId();
|
|
1321
|
+
}
|
|
1322
|
+
function setErrorResponseHeaders(headerSetter, correlationId) {
|
|
1323
|
+
headerSetter("x-correlation-id", correlationId);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/middleware/error-boundary.ts
|
|
1327
|
+
function createErrorBoundary(options = {}) {
|
|
1328
|
+
const { debug = false } = options;
|
|
1329
|
+
const middlewareFn = async (ctx, next) => {
|
|
1330
|
+
try {
|
|
1331
|
+
await next();
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
if (ctx.response.sent) {
|
|
1334
|
+
if (debug) {
|
|
1335
|
+
console.error("Error occurred after response was sent:", error);
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (debug) {
|
|
1340
|
+
console.error("Error boundary caught error:", error);
|
|
1341
|
+
}
|
|
1342
|
+
const correlationId = extractOrGenerateCorrelationId(ctx.request.header);
|
|
1343
|
+
const errorResponse = formatErrorResponse(error);
|
|
1344
|
+
errorResponse.correlationId = correlationId;
|
|
1345
|
+
setErrorResponseHeaders(ctx.response.header, correlationId);
|
|
1346
|
+
ctx.response.status(errorResponse.status).json(errorResponse);
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
return {
|
|
1350
|
+
name: "ErrorBoundary",
|
|
1351
|
+
execute: middlewareFn,
|
|
1352
|
+
debug
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
733
1356
|
// src/server/request-handler.ts
|
|
734
1357
|
function createRequestHandler(serverInstance) {
|
|
735
1358
|
return async (req, res) => {
|
|
@@ -738,32 +1361,20 @@ function createRequestHandler(serverInstance) {
|
|
|
738
1361
|
parseBody: true
|
|
739
1362
|
// Enable automatic body parsing
|
|
740
1363
|
});
|
|
741
|
-
const
|
|
1364
|
+
const errorBoundary = createErrorBoundary();
|
|
1365
|
+
const allMiddleware = [errorBoundary, ...serverInstance.middleware];
|
|
1366
|
+
const handler = compose(allMiddleware);
|
|
742
1367
|
await runWithContext(context, async () => {
|
|
743
|
-
|
|
744
|
-
|
|
1368
|
+
await handler(context, async () => {
|
|
1369
|
+
if (!context.response.sent) {
|
|
1370
|
+
await serverInstance.router.handleRequest(context);
|
|
745
1371
|
if (!context.response.sent) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
error: "Not Found",
|
|
750
|
-
message: `Route not found: ${context.request.method} ${context.request.path}`
|
|
751
|
-
});
|
|
752
|
-
}
|
|
1372
|
+
throw new NotFoundError(
|
|
1373
|
+
`Route not found: ${context.request.method} ${context.request.path}`
|
|
1374
|
+
);
|
|
753
1375
|
}
|
|
754
|
-
});
|
|
755
|
-
} catch (error) {
|
|
756
|
-
console.error("Error processing request:", error);
|
|
757
|
-
if (!context.response.sent) {
|
|
758
|
-
context.response.json(
|
|
759
|
-
{
|
|
760
|
-
error: "Internal Server Error",
|
|
761
|
-
message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
|
|
762
|
-
},
|
|
763
|
-
500
|
|
764
|
-
);
|
|
765
1376
|
}
|
|
766
|
-
}
|
|
1377
|
+
});
|
|
767
1378
|
});
|
|
768
1379
|
} catch (error) {
|
|
769
1380
|
console.error("Error creating context:", error);
|
|
@@ -1604,81 +2215,8 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1604
2215
|
};
|
|
1605
2216
|
}
|
|
1606
2217
|
|
|
1607
|
-
// src/router/
|
|
1608
|
-
|
|
1609
|
-
if (options.log) {
|
|
1610
|
-
console.error("Route error:", error);
|
|
1611
|
-
}
|
|
1612
|
-
const status = getErrorStatus(error);
|
|
1613
|
-
const response = {
|
|
1614
|
-
error: getErrorType(error),
|
|
1615
|
-
message: getErrorMessage(error)
|
|
1616
|
-
};
|
|
1617
|
-
if (options.detailed) {
|
|
1618
|
-
if (error instanceof Error) {
|
|
1619
|
-
response.stack = error.stack;
|
|
1620
|
-
}
|
|
1621
|
-
if (error && typeof error === "object" && "details" in error && error.details) {
|
|
1622
|
-
response.details = error.details;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
ctx.response.status(status).json(response);
|
|
1626
|
-
}
|
|
1627
|
-
function getErrorStatus(error) {
|
|
1628
|
-
if (error && typeof error === "object") {
|
|
1629
|
-
if ("status" in error && typeof error.status === "number") {
|
|
1630
|
-
return error.status;
|
|
1631
|
-
}
|
|
1632
|
-
if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
1633
|
-
return error.statusCode;
|
|
1634
|
-
}
|
|
1635
|
-
if ("code" in error && typeof error.code === "string") {
|
|
1636
|
-
return getStatusFromCode(error.code);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
return 500;
|
|
1640
|
-
}
|
|
1641
|
-
function getStatusFromCode(code) {
|
|
1642
|
-
switch (code) {
|
|
1643
|
-
case "NOT_FOUND":
|
|
1644
|
-
return 404;
|
|
1645
|
-
case "UNAUTHORIZED":
|
|
1646
|
-
return 401;
|
|
1647
|
-
case "FORBIDDEN":
|
|
1648
|
-
return 403;
|
|
1649
|
-
case "BAD_REQUEST":
|
|
1650
|
-
return 400;
|
|
1651
|
-
case "CONFLICT":
|
|
1652
|
-
return 409;
|
|
1653
|
-
default:
|
|
1654
|
-
return 500;
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
function getErrorType(error) {
|
|
1658
|
-
if (error && typeof error === "object") {
|
|
1659
|
-
if ("type" in error && typeof error.type === "string") {
|
|
1660
|
-
return error.type;
|
|
1661
|
-
}
|
|
1662
|
-
if ("name" in error && typeof error.name === "string") {
|
|
1663
|
-
return error.name;
|
|
1664
|
-
}
|
|
1665
|
-
if (error instanceof Error) {
|
|
1666
|
-
return error.constructor.name;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
return "Error";
|
|
1670
|
-
}
|
|
1671
|
-
function getErrorMessage(error) {
|
|
1672
|
-
if (error instanceof Error) {
|
|
1673
|
-
return error.message;
|
|
1674
|
-
}
|
|
1675
|
-
if (error && typeof error === "object") {
|
|
1676
|
-
if ("message" in error && typeof error.message === "string") {
|
|
1677
|
-
return error.message;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
return String(error);
|
|
1681
|
-
}
|
|
2218
|
+
// src/router/validation/schema.ts
|
|
2219
|
+
import { z as z6 } from "zod";
|
|
1682
2220
|
|
|
1683
2221
|
// src/router/validation/body.ts
|
|
1684
2222
|
import { z as z2 } from "zod";
|
|
@@ -1719,35 +2257,45 @@ function validateResponse(response, schema) {
|
|
|
1719
2257
|
// src/router/validation/schema.ts
|
|
1720
2258
|
function createRequestValidator(schema, debug = false) {
|
|
1721
2259
|
const middlewareFn = async (ctx, next) => {
|
|
1722
|
-
const errors = {};
|
|
1723
2260
|
if (schema.params && ctx.request.params) {
|
|
1724
2261
|
try {
|
|
1725
2262
|
ctx.request.params = validateParams(ctx.request.params, schema.params);
|
|
1726
2263
|
} catch (error) {
|
|
1727
|
-
|
|
2264
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2265
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2266
|
+
throw new ValidationError("Request validation failed", {
|
|
2267
|
+
fields: fieldErrors,
|
|
2268
|
+
errorCount,
|
|
2269
|
+
section: "params"
|
|
2270
|
+
});
|
|
1728
2271
|
}
|
|
1729
2272
|
}
|
|
1730
2273
|
if (schema.query && ctx.request.query) {
|
|
1731
2274
|
try {
|
|
1732
2275
|
ctx.request.query = validateQuery(ctx.request.query, schema.query);
|
|
1733
2276
|
} catch (error) {
|
|
1734
|
-
|
|
2277
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2278
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2279
|
+
throw new ValidationError("Request validation failed", {
|
|
2280
|
+
fields: fieldErrors,
|
|
2281
|
+
errorCount,
|
|
2282
|
+
section: "query"
|
|
2283
|
+
});
|
|
1735
2284
|
}
|
|
1736
2285
|
}
|
|
1737
2286
|
if (schema.body) {
|
|
1738
2287
|
try {
|
|
1739
2288
|
ctx.request.body = validateBody(ctx.request.body, schema.body);
|
|
1740
2289
|
} catch (error) {
|
|
1741
|
-
|
|
2290
|
+
const fieldErrors = extractZodFieldErrors(error);
|
|
2291
|
+
const errorCount = fieldErrors.reduce((sum, fe) => sum + fe.messages.length, 0);
|
|
2292
|
+
throw new ValidationError("Request validation failed", {
|
|
2293
|
+
fields: fieldErrors,
|
|
2294
|
+
errorCount,
|
|
2295
|
+
section: "body"
|
|
2296
|
+
});
|
|
1742
2297
|
}
|
|
1743
2298
|
}
|
|
1744
|
-
if (Object.keys(errors).length > 0) {
|
|
1745
|
-
ctx.response.status(400).json({
|
|
1746
|
-
error: "Validation Error",
|
|
1747
|
-
details: errors
|
|
1748
|
-
});
|
|
1749
|
-
return;
|
|
1750
|
-
}
|
|
1751
2299
|
await next();
|
|
1752
2300
|
};
|
|
1753
2301
|
return {
|
|
@@ -1766,12 +2314,11 @@ function createResponseValidator(responseSchema, debug = false) {
|
|
|
1766
2314
|
return originalJson.call(ctx.response, validatedBody, status);
|
|
1767
2315
|
} catch (error) {
|
|
1768
2316
|
ctx.response.json = originalJson;
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2317
|
+
throw new InternalServerError("Response validation failed", {
|
|
2318
|
+
responseSchema: responseSchema.description || "Unknown schema",
|
|
2319
|
+
validationError: extractZodFieldErrors(error),
|
|
2320
|
+
originalResponse: body
|
|
1773
2321
|
});
|
|
1774
|
-
return ctx.response;
|
|
1775
2322
|
}
|
|
1776
2323
|
};
|
|
1777
2324
|
await next();
|
|
@@ -1782,11 +2329,25 @@ function createResponseValidator(responseSchema, debug = false) {
|
|
|
1782
2329
|
debug
|
|
1783
2330
|
};
|
|
1784
2331
|
}
|
|
1785
|
-
function
|
|
1786
|
-
if (error
|
|
1787
|
-
|
|
2332
|
+
function extractZodFieldErrors(error) {
|
|
2333
|
+
if (error instanceof z6.ZodError) {
|
|
2334
|
+
const fieldErrorMap = /* @__PURE__ */ new Map();
|
|
2335
|
+
for (const issue of error.issues) {
|
|
2336
|
+
const fieldPath = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2337
|
+
if (!fieldErrorMap.has(fieldPath)) {
|
|
2338
|
+
fieldErrorMap.set(fieldPath, []);
|
|
2339
|
+
}
|
|
2340
|
+
fieldErrorMap.get(fieldPath).push(issue.message);
|
|
2341
|
+
}
|
|
2342
|
+
return Array.from(fieldErrorMap.entries()).map(([field, messages]) => ({
|
|
2343
|
+
field,
|
|
2344
|
+
messages
|
|
2345
|
+
}));
|
|
1788
2346
|
}
|
|
1789
|
-
|
|
2347
|
+
if (error instanceof Error) {
|
|
2348
|
+
return [{ field: "unknown", messages: [error.message] }];
|
|
2349
|
+
}
|
|
2350
|
+
return [{ field: "unknown", messages: [String(error)] }];
|
|
1790
2351
|
}
|
|
1791
2352
|
|
|
1792
2353
|
// src/router/handlers/executor.ts
|
|
@@ -2221,8 +2782,7 @@ function createRouter(options) {
|
|
|
2221
2782
|
const match = matcher.match(path6, method);
|
|
2222
2783
|
if (!match) {
|
|
2223
2784
|
console.log(`\u274C No match found for: ${method} ${path6}`);
|
|
2224
|
-
|
|
2225
|
-
return;
|
|
2785
|
+
throw new NotFoundError("Not found");
|
|
2226
2786
|
}
|
|
2227
2787
|
console.log(`\u2705 Route matched: ${method} ${path6}`);
|
|
2228
2788
|
console.log(` Params: ${JSON.stringify(match.params)}`);
|
|
@@ -2237,14 +2797,7 @@ function createRouter(options) {
|
|
|
2237
2797
|
return;
|
|
2238
2798
|
}
|
|
2239
2799
|
ctx.request.params = match.params;
|
|
2240
|
-
|
|
2241
|
-
await executeHandler(ctx, match.route, match.params);
|
|
2242
|
-
} catch (error) {
|
|
2243
|
-
handleRouteError(ctx, error, {
|
|
2244
|
-
detailed: process.env.NODE_ENV !== "production",
|
|
2245
|
-
log: true
|
|
2246
|
-
});
|
|
2247
|
-
}
|
|
2800
|
+
await executeHandler(ctx, match.route, match.params);
|
|
2248
2801
|
},
|
|
2249
2802
|
/**
|
|
2250
2803
|
* Get all registered routes (using optimized registry)
|
|
@@ -2305,7 +2858,7 @@ function createRouter(options) {
|
|
|
2305
2858
|
}
|
|
2306
2859
|
|
|
2307
2860
|
// src/server/create.ts
|
|
2308
|
-
var
|
|
2861
|
+
var DEFAULT_OPTIONS2 = {
|
|
2309
2862
|
port: 3e3,
|
|
2310
2863
|
host: "localhost",
|
|
2311
2864
|
routesDir: "./routes",
|
|
@@ -2316,7 +2869,7 @@ var DEFAULT_OPTIONS = {
|
|
|
2316
2869
|
plugins: []
|
|
2317
2870
|
};
|
|
2318
2871
|
function createServerOptions(options = {}) {
|
|
2319
|
-
const baseOptions = { ...
|
|
2872
|
+
const baseOptions = { ...DEFAULT_OPTIONS2 };
|
|
2320
2873
|
setRuntimeConfig({ routesDir: options.routesDir || baseOptions.routesDir });
|
|
2321
2874
|
return {
|
|
2322
2875
|
port: options.port ?? baseOptions.port,
|
|
@@ -2436,6 +2989,90 @@ function create3(options = {}) {
|
|
|
2436
2989
|
return serverInstance;
|
|
2437
2990
|
}
|
|
2438
2991
|
|
|
2992
|
+
// src/errors/unauthorized-error.ts
|
|
2993
|
+
var UnauthorizedError = class extends BlaizeError {
|
|
2994
|
+
/**
|
|
2995
|
+
* Creates a new UnauthorizedError instance
|
|
2996
|
+
*
|
|
2997
|
+
* @param title - Human-readable error message
|
|
2998
|
+
* @param details - Optional authentication context
|
|
2999
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3000
|
+
*/
|
|
3001
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3002
|
+
super(
|
|
3003
|
+
"UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
3004
|
+
title,
|
|
3005
|
+
401,
|
|
3006
|
+
// HTTP 401 Unauthorized
|
|
3007
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3008
|
+
details
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
// src/errors/forbidden-error.ts
|
|
3014
|
+
var ForbiddenError = class extends BlaizeError {
|
|
3015
|
+
/**
|
|
3016
|
+
* Creates a new ForbiddenError instance
|
|
3017
|
+
*
|
|
3018
|
+
* @param title - Human-readable error message
|
|
3019
|
+
* @param details - Optional permission context
|
|
3020
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3021
|
+
*/
|
|
3022
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3023
|
+
super(
|
|
3024
|
+
"FORBIDDEN" /* FORBIDDEN */,
|
|
3025
|
+
title,
|
|
3026
|
+
403,
|
|
3027
|
+
// HTTP 403 Forbidden
|
|
3028
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3029
|
+
details
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
};
|
|
3033
|
+
|
|
3034
|
+
// src/errors/conflict-error.ts
|
|
3035
|
+
var ConflictError = class extends BlaizeError {
|
|
3036
|
+
/**
|
|
3037
|
+
* Creates a new ConflictError instance
|
|
3038
|
+
*
|
|
3039
|
+
* @param title - Human-readable error message
|
|
3040
|
+
* @param details - Optional conflict context
|
|
3041
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3042
|
+
*/
|
|
3043
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3044
|
+
super(
|
|
3045
|
+
"CONFLICT" /* CONFLICT */,
|
|
3046
|
+
title,
|
|
3047
|
+
409,
|
|
3048
|
+
// HTTP 409 Conflict
|
|
3049
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3050
|
+
details
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
|
|
3055
|
+
// src/errors/rate-limit-error.ts
|
|
3056
|
+
var RateLimitError = class extends BlaizeError {
|
|
3057
|
+
/**
|
|
3058
|
+
* Creates a new RateLimitError instance
|
|
3059
|
+
*
|
|
3060
|
+
* @param title - Human-readable error message
|
|
3061
|
+
* @param details - Optional rate limit context
|
|
3062
|
+
* @param correlationId - Optional correlation ID (uses current context if not provided)
|
|
3063
|
+
*/
|
|
3064
|
+
constructor(title, details = void 0, correlationId = void 0) {
|
|
3065
|
+
super(
|
|
3066
|
+
"RATE_LIMITED" /* RATE_LIMITED */,
|
|
3067
|
+
title,
|
|
3068
|
+
429,
|
|
3069
|
+
// HTTP 429 Too Many Requests
|
|
3070
|
+
correlationId ?? getCurrentCorrelationId(),
|
|
3071
|
+
details
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
|
|
2439
3076
|
// src/index.ts
|
|
2440
3077
|
var VERSION = "0.1.0";
|
|
2441
3078
|
var ServerAPI = { createServer: create3 };
|
|
@@ -2466,11 +3103,21 @@ var Blaize = {
|
|
|
2466
3103
|
var index_default = Blaize;
|
|
2467
3104
|
export {
|
|
2468
3105
|
Blaize,
|
|
3106
|
+
BlaizeError,
|
|
3107
|
+
ConflictError,
|
|
3108
|
+
ErrorSeverity,
|
|
3109
|
+
ErrorType,
|
|
3110
|
+
ForbiddenError,
|
|
3111
|
+
InternalServerError,
|
|
2469
3112
|
MiddlewareAPI,
|
|
3113
|
+
NotFoundError,
|
|
2470
3114
|
PluginsAPI,
|
|
3115
|
+
RateLimitError,
|
|
2471
3116
|
RouterAPI,
|
|
2472
3117
|
ServerAPI,
|
|
3118
|
+
UnauthorizedError,
|
|
2473
3119
|
VERSION,
|
|
3120
|
+
ValidationError,
|
|
2474
3121
|
compose,
|
|
2475
3122
|
createDeleteRoute,
|
|
2476
3123
|
createGetRoute,
|
|
@@ -2482,6 +3129,7 @@ export {
|
|
|
2482
3129
|
createPostRoute,
|
|
2483
3130
|
createPutRoute,
|
|
2484
3131
|
create3 as createServer,
|
|
2485
|
-
index_default as default
|
|
3132
|
+
index_default as default,
|
|
3133
|
+
isBodyParseError
|
|
2486
3134
|
};
|
|
2487
3135
|
//# sourceMappingURL=index.js.map
|