blaizejs 0.2.3 → 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/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * blaizejs v0.2.3
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
 
@@ -341,7 +606,7 @@ function validateSchema(method, schema) {
341
606
  }
342
607
 
343
608
  // src/server/create.ts
344
- var import_node_async_hooks2 = require("async_hooks");
609
+ var import_node_async_hooks3 = require("async_hooks");
345
610
  var import_node_events = __toESM(require("events"), 1);
346
611
 
347
612
  // src/server/start.ts
@@ -437,8 +702,474 @@ function runWithContext(context, callback) {
437
702
  return contextStorage.run(context, callback);
438
703
  }
439
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
+
440
1152
  // src/context/create.ts
441
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
+ };
442
1173
  function parseRequestUrl(req) {
443
1174
  const originalUrl = req.url || "/";
444
1175
  const host = req.headers.host || "localhost";
@@ -503,7 +1234,7 @@ async function createContext(req, res, options = {}) {
503
1234
  };
504
1235
  ctx.response = createResponseObject(res, responseState, ctx);
505
1236
  if (options.parseBody) {
506
- await parseBodyIfNeeded(req, ctx);
1237
+ await parseBodyIfNeeded(req, ctx, options);
507
1238
  }
508
1239
  return ctx;
509
1240
  }
@@ -679,34 +1410,60 @@ function createStreamResponder(res, responseState) {
679
1410
  });
680
1411
  };
681
1412
  }
682
- async function parseBodyIfNeeded(req, ctx) {
1413
+ async function parseBodyIfNeeded(req, ctx, options = {}) {
683
1414
  if (shouldSkipParsing(req.method)) {
684
1415
  return;
685
1416
  }
686
1417
  const contentType = req.headers["content-type"] || "";
687
1418
  const contentLength = parseInt(req.headers["content-length"] || "0", 10);
688
- if (contentLength === 0 || contentLength > 1048576) {
1419
+ if (contentLength === 0) {
689
1420
  return;
690
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
+ };
691
1434
  try {
692
- await parseBodyByContentType(req, ctx, contentType);
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
+ }
693
1458
  } catch (error) {
694
- setBodyError(ctx, "body_read_error", "Error reading request body", error);
1459
+ const errorType = contentType.includes("multipart") ? "multipart_parse_error" : "body_read_error";
1460
+ setBodyError(ctx, errorType, "Error reading request body", error);
695
1461
  }
696
1462
  }
697
1463
  function shouldSkipParsing(method) {
698
1464
  const skipMethods = ["GET", "HEAD", "OPTIONS"];
699
1465
  return skipMethods.includes(method || "GET");
700
1466
  }
701
- async function parseBodyByContentType(req, ctx, contentType) {
702
- if (contentType.includes("application/json")) {
703
- await parseJsonBody(req, ctx);
704
- } else if (contentType.includes("application/x-www-form-urlencoded")) {
705
- await parseFormUrlEncodedBody(req, ctx);
706
- } else if (contentType.includes("text/")) {
707
- await parseTextBody(req, ctx);
708
- }
709
- }
710
1467
  async function parseJsonBody(req, ctx) {
711
1468
  const body = await readRequestBody(req);
712
1469
  if (!body) {
@@ -758,8 +1515,27 @@ async function parseTextBody(req, ctx) {
758
1515
  ctx.request.body = body;
759
1516
  }
760
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
+ }
761
1536
  function setBodyError(ctx, type, message, error) {
762
- ctx.state._bodyError = { type, message, error };
1537
+ const bodyError = { type, message, error };
1538
+ ctx.state._bodyError = bodyError;
763
1539
  }
764
1540
  async function readRequestBody(req) {
765
1541
  return new Promise((resolve3, reject) => {
@@ -776,6 +1552,107 @@ async function readRequestBody(req) {
776
1552
  });
777
1553
  }
778
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
+
779
1656
  // src/server/request-handler.ts
780
1657
  function createRequestHandler(serverInstance) {
781
1658
  return async (req, res) => {
@@ -784,32 +1661,20 @@ function createRequestHandler(serverInstance) {
784
1661
  parseBody: true
785
1662
  // Enable automatic body parsing
786
1663
  });
787
- const handler = compose(serverInstance.middleware);
1664
+ const errorBoundary = createErrorBoundary();
1665
+ const allMiddleware = [errorBoundary, ...serverInstance.middleware];
1666
+ const handler = compose(allMiddleware);
788
1667
  await runWithContext(context, async () => {
789
- try {
790
- await handler(context, async () => {
1668
+ await handler(context, async () => {
1669
+ if (!context.response.sent) {
1670
+ await serverInstance.router.handleRequest(context);
791
1671
  if (!context.response.sent) {
792
- await serverInstance.router.handleRequest(context);
793
- if (!context.response.sent) {
794
- context.response.status(404).json({
795
- error: "Not Found",
796
- message: `Route not found: ${context.request.method} ${context.request.path}`
797
- });
798
- }
1672
+ throw new NotFoundError(
1673
+ `Route not found: ${context.request.method} ${context.request.path}`
1674
+ );
799
1675
  }
800
- });
801
- } catch (error) {
802
- console.error("Error processing request:", error);
803
- if (!context.response.sent) {
804
- context.response.json(
805
- {
806
- error: "Internal Server Error",
807
- message: process.env.NODE_ENV === "development" ? error || "Unknown error" : "An error occurred processing your request"
808
- },
809
- 500
810
- );
811
1676
  }
812
- }
1677
+ });
813
1678
  });
814
1679
  } catch (error) {
815
1680
  console.error("Error creating context:", error);
@@ -1651,81 +2516,8 @@ function watchRoutes(routesDir, options = {}) {
1651
2516
  };
1652
2517
  }
1653
2518
 
1654
- // src/router/handlers/error.ts
1655
- function handleRouteError(ctx, error, options = {}) {
1656
- if (options.log) {
1657
- console.error("Route error:", error);
1658
- }
1659
- const status = getErrorStatus(error);
1660
- const response = {
1661
- error: getErrorType(error),
1662
- message: getErrorMessage(error)
1663
- };
1664
- if (options.detailed) {
1665
- if (error instanceof Error) {
1666
- response.stack = error.stack;
1667
- }
1668
- if (error && typeof error === "object" && "details" in error && error.details) {
1669
- response.details = error.details;
1670
- }
1671
- }
1672
- ctx.response.status(status).json(response);
1673
- }
1674
- function getErrorStatus(error) {
1675
- if (error && typeof error === "object") {
1676
- if ("status" in error && typeof error.status === "number") {
1677
- return error.status;
1678
- }
1679
- if ("statusCode" in error && typeof error.statusCode === "number") {
1680
- return error.statusCode;
1681
- }
1682
- if ("code" in error && typeof error.code === "string") {
1683
- return getStatusFromCode(error.code);
1684
- }
1685
- }
1686
- return 500;
1687
- }
1688
- function getStatusFromCode(code) {
1689
- switch (code) {
1690
- case "NOT_FOUND":
1691
- return 404;
1692
- case "UNAUTHORIZED":
1693
- return 401;
1694
- case "FORBIDDEN":
1695
- return 403;
1696
- case "BAD_REQUEST":
1697
- return 400;
1698
- case "CONFLICT":
1699
- return 409;
1700
- default:
1701
- return 500;
1702
- }
1703
- }
1704
- function getErrorType(error) {
1705
- if (error && typeof error === "object") {
1706
- if ("type" in error && typeof error.type === "string") {
1707
- return error.type;
1708
- }
1709
- if ("name" in error && typeof error.name === "string") {
1710
- return error.name;
1711
- }
1712
- if (error instanceof Error) {
1713
- return error.constructor.name;
1714
- }
1715
- }
1716
- return "Error";
1717
- }
1718
- function getErrorMessage(error) {
1719
- if (error instanceof Error) {
1720
- return error.message;
1721
- }
1722
- if (error && typeof error === "object") {
1723
- if ("message" in error && typeof error.message === "string") {
1724
- return error.message;
1725
- }
1726
- }
1727
- return String(error);
1728
- }
2519
+ // src/router/validation/schema.ts
2520
+ var import_zod6 = require("zod");
1729
2521
 
1730
2522
  // src/router/validation/body.ts
1731
2523
  var import_zod2 = require("zod");
@@ -1764,37 +2556,49 @@ function validateResponse(response, schema) {
1764
2556
  }
1765
2557
 
1766
2558
  // src/router/validation/schema.ts
2559
+ init_internal_server_error();
2560
+ init_validation_error();
1767
2561
  function createRequestValidator(schema, debug = false) {
1768
2562
  const middlewareFn = async (ctx, next) => {
1769
- const errors = {};
1770
2563
  if (schema.params && ctx.request.params) {
1771
2564
  try {
1772
2565
  ctx.request.params = validateParams(ctx.request.params, schema.params);
1773
2566
  } catch (error) {
1774
- errors.params = formatValidationError(error);
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
+ });
1775
2574
  }
1776
2575
  }
1777
2576
  if (schema.query && ctx.request.query) {
1778
2577
  try {
1779
2578
  ctx.request.query = validateQuery(ctx.request.query, schema.query);
1780
2579
  } catch (error) {
1781
- errors.query = formatValidationError(error);
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
+ });
1782
2587
  }
1783
2588
  }
1784
2589
  if (schema.body) {
1785
2590
  try {
1786
2591
  ctx.request.body = validateBody(ctx.request.body, schema.body);
1787
2592
  } catch (error) {
1788
- errors.body = formatValidationError(error);
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
+ });
1789
2600
  }
1790
2601
  }
1791
- if (Object.keys(errors).length > 0) {
1792
- ctx.response.status(400).json({
1793
- error: "Validation Error",
1794
- details: errors
1795
- });
1796
- return;
1797
- }
1798
2602
  await next();
1799
2603
  };
1800
2604
  return {
@@ -1813,12 +2617,11 @@ function createResponseValidator(responseSchema, debug = false) {
1813
2617
  return originalJson.call(ctx.response, validatedBody, status);
1814
2618
  } catch (error) {
1815
2619
  ctx.response.json = originalJson;
1816
- console.error("Response validation error:", error);
1817
- ctx.response.status(500).json({
1818
- error: "Internal Server Error",
1819
- message: "Response validation failed"
2620
+ throw new InternalServerError("Response validation failed", {
2621
+ responseSchema: responseSchema.description || "Unknown schema",
2622
+ validationError: extractZodFieldErrors(error),
2623
+ originalResponse: body
1820
2624
  });
1821
- return ctx.response;
1822
2625
  }
1823
2626
  };
1824
2627
  await next();
@@ -1829,11 +2632,25 @@ function createResponseValidator(responseSchema, debug = false) {
1829
2632
  debug
1830
2633
  };
1831
2634
  }
1832
- function formatValidationError(error) {
1833
- if (error && typeof error === "object" && "format" in error && typeof error.format === "function") {
1834
- return error.format();
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] }];
1835
2652
  }
1836
- return error instanceof Error ? error.message : String(error);
2653
+ return [{ field: "unknown", messages: [String(error)] }];
1837
2654
  }
1838
2655
 
1839
2656
  // src/router/handlers/executor.ts
@@ -2268,8 +3085,7 @@ function createRouter(options) {
2268
3085
  const match = matcher.match(path6, method);
2269
3086
  if (!match) {
2270
3087
  console.log(`\u274C No match found for: ${method} ${path6}`);
2271
- ctx.response.status(404).json({ error: "Not Found" });
2272
- return;
3088
+ throw new NotFoundError("Not found");
2273
3089
  }
2274
3090
  console.log(`\u2705 Route matched: ${method} ${path6}`);
2275
3091
  console.log(` Params: ${JSON.stringify(match.params)}`);
@@ -2284,14 +3100,7 @@ function createRouter(options) {
2284
3100
  return;
2285
3101
  }
2286
3102
  ctx.request.params = match.params;
2287
- try {
2288
- await executeHandler(ctx, match.route, match.params);
2289
- } catch (error) {
2290
- handleRouteError(ctx, error, {
2291
- detailed: process.env.NODE_ENV !== "production",
2292
- log: true
2293
- });
2294
- }
3103
+ await executeHandler(ctx, match.route, match.params);
2295
3104
  },
2296
3105
  /**
2297
3106
  * Get all registered routes (using optimized registry)
@@ -2352,7 +3161,7 @@ function createRouter(options) {
2352
3161
  }
2353
3162
 
2354
3163
  // src/server/create.ts
2355
- var DEFAULT_OPTIONS = {
3164
+ var DEFAULT_OPTIONS2 = {
2356
3165
  port: 3e3,
2357
3166
  host: "localhost",
2358
3167
  routesDir: "./routes",
@@ -2363,7 +3172,7 @@ var DEFAULT_OPTIONS = {
2363
3172
  plugins: []
2364
3173
  };
2365
3174
  function createServerOptions(options = {}) {
2366
- const baseOptions = { ...DEFAULT_OPTIONS };
3175
+ const baseOptions = { ...DEFAULT_OPTIONS2 };
2367
3176
  setRuntimeConfig({ routesDir: options.routesDir || baseOptions.routesDir });
2368
3177
  return {
2369
3178
  port: options.port ?? baseOptions.port,
@@ -2443,7 +3252,7 @@ function create3(options = {}) {
2443
3252
  const { port, host, middleware, plugins } = validatedOptions;
2444
3253
  const initialMiddleware = Array.isArray(middleware) ? [...middleware] : [];
2445
3254
  const initialPlugins = Array.isArray(plugins) ? [...plugins] : [];
2446
- const contextStorage2 = new import_node_async_hooks2.AsyncLocalStorage();
3255
+ const contextStorage2 = new import_node_async_hooks3.AsyncLocalStorage();
2447
3256
  const router = createRouter({
2448
3257
  routesDir: validatedOptions.routesDir,
2449
3258
  watchMode: process.env.NODE_ENV === "development"
@@ -2483,7 +3292,106 @@ function create3(options = {}) {
2483
3292
  return serverInstance;
2484
3293
  }
2485
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
+
2486
3393
  // src/index.ts
3394
+ init_internal_server_error();
2487
3395
  var VERSION = "0.1.0";
2488
3396
  var ServerAPI = { createServer: create3 };
2489
3397
  var RouterAPI = {
@@ -2514,11 +3422,21 @@ var index_default = Blaize;
2514
3422
  // Annotate the CommonJS export names for ESM import in node:
2515
3423
  0 && (module.exports = {
2516
3424
  Blaize,
3425
+ BlaizeError,
3426
+ ConflictError,
3427
+ ErrorSeverity,
3428
+ ErrorType,
3429
+ ForbiddenError,
3430
+ InternalServerError,
2517
3431
  MiddlewareAPI,
3432
+ NotFoundError,
2518
3433
  PluginsAPI,
3434
+ RateLimitError,
2519
3435
  RouterAPI,
2520
3436
  ServerAPI,
3437
+ UnauthorizedError,
2521
3438
  VERSION,
3439
+ ValidationError,
2522
3440
  compose,
2523
3441
  createDeleteRoute,
2524
3442
  createGetRoute,
@@ -2529,6 +3447,7 @@ var index_default = Blaize;
2529
3447
  createPlugin,
2530
3448
  createPostRoute,
2531
3449
  createPutRoute,
2532
- createServer
3450
+ createServer,
3451
+ isBodyParseError
2533
3452
  });
2534
3453
  //# sourceMappingURL=index.cjs.map