http-response-kit 1.0.0 → 2.0.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/LICENSE +21 -21
- package/README.md +357 -272
- package/dist/chunk-373JVXMN.mjs +1199 -0
- package/dist/chunk-373JVXMN.mjs.map +1 -0
- package/dist/chunk-FFRB2EUE.mjs +36 -0
- package/dist/chunk-FFRB2EUE.mjs.map +1 -0
- package/dist/express.d.mts +50 -0
- package/dist/express.d.ts +50 -0
- package/dist/express.js +1155 -0
- package/dist/express.js.map +1 -0
- package/dist/express.mjs +28 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.d.mts +45 -0
- package/dist/fastify.d.ts +45 -0
- package/dist/fastify.js +1170 -0
- package/dist/fastify.js.map +1 -0
- package/dist/fastify.mjs +43 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/hono.d.mts +33 -0
- package/dist/hono.d.ts +33 -0
- package/dist/hono.js +1139 -0
- package/dist/hono.js.map +1 -0
- package/dist/hono.mjs +18 -0
- package/dist/hono.mjs.map +1 -0
- package/dist/index.d.mts +75 -493
- package/dist/index.d.ts +75 -493
- package/dist/index.js +480 -180
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +18 -891
- package/dist/index.mjs.map +1 -0
- package/dist/kit-nHfihlKE.d.mts +753 -0
- package/dist/kit-nHfihlKE.d.ts +753 -0
- package/dist/koa.d.mts +38 -0
- package/dist/koa.d.ts +38 -0
- package/dist/koa.js +1150 -0
- package/dist/koa.js.map +1 -0
- package/dist/koa.mjs +24 -0
- package/dist/koa.mjs.map +1 -0
- package/dist/schemas.d.mts +452 -0
- package/dist/schemas.d.ts +452 -0
- package/dist/schemas.js +124 -0
- package/dist/schemas.js.map +1 -0
- package/dist/schemas.mjs +119 -0
- package/dist/schemas.mjs.map +1 -0
- package/dist/shared-Czwmz6Xa.d.ts +22 -0
- package/dist/shared-DYq1Fic4.d.mts +22 -0
- package/package.json +104 -49
package/dist/hono.js
ADDED
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/constants/error-definitions.ts
|
|
4
|
+
var HttpErrorDefinitions = {
|
|
5
|
+
// ========================================================================
|
|
6
|
+
// 4xx Client Errors
|
|
7
|
+
// ========================================================================
|
|
8
|
+
[400 /* BAD_REQUEST */]: {
|
|
9
|
+
type: "bad_request",
|
|
10
|
+
title: "Bad Request",
|
|
11
|
+
code: 400,
|
|
12
|
+
details: "The request cannot be processed due to invalid syntax or missing parameters."
|
|
13
|
+
},
|
|
14
|
+
[401 /* UNAUTHORIZED */]: {
|
|
15
|
+
type: "unauthorized",
|
|
16
|
+
title: "Unauthorized",
|
|
17
|
+
code: 401,
|
|
18
|
+
details: "Authentication is required and has failed or not yet been provided."
|
|
19
|
+
},
|
|
20
|
+
[402 /* PAYMENT_REQUIRED */]: {
|
|
21
|
+
type: "payment_required",
|
|
22
|
+
title: "Payment Required",
|
|
23
|
+
code: 402,
|
|
24
|
+
details: "Payment is required to access this resource."
|
|
25
|
+
},
|
|
26
|
+
[403 /* FORBIDDEN */]: {
|
|
27
|
+
type: "forbidden",
|
|
28
|
+
title: "Forbidden",
|
|
29
|
+
code: 403,
|
|
30
|
+
details: "The server understood the request but refuses to authorize it."
|
|
31
|
+
},
|
|
32
|
+
[404 /* NOT_FOUND */]: {
|
|
33
|
+
type: "not_found",
|
|
34
|
+
title: "Not Found",
|
|
35
|
+
code: 404,
|
|
36
|
+
details: "The requested resource could not be found."
|
|
37
|
+
},
|
|
38
|
+
[405 /* METHOD_NOT_ALLOWED */]: {
|
|
39
|
+
type: "method_not_allowed",
|
|
40
|
+
title: "Method Not Allowed",
|
|
41
|
+
code: 405,
|
|
42
|
+
details: "The method specified in the request is not allowed for the resource."
|
|
43
|
+
},
|
|
44
|
+
[406 /* NOT_ACCEPTABLE */]: {
|
|
45
|
+
type: "not_acceptable",
|
|
46
|
+
title: "Not Acceptable",
|
|
47
|
+
code: 406,
|
|
48
|
+
details: "The resource is not available in a format acceptable to the client."
|
|
49
|
+
},
|
|
50
|
+
[407 /* PROXY_AUTHENTICATION_REQUIRED */]: {
|
|
51
|
+
type: "proxy_authentication_required",
|
|
52
|
+
title: "Proxy Authentication Required",
|
|
53
|
+
code: 407,
|
|
54
|
+
details: "Authentication with the proxy is required."
|
|
55
|
+
},
|
|
56
|
+
[408 /* REQUEST_TIMEOUT */]: {
|
|
57
|
+
type: "request_timeout",
|
|
58
|
+
title: "Request Timeout",
|
|
59
|
+
code: 408,
|
|
60
|
+
details: "The server timed out waiting for the request."
|
|
61
|
+
},
|
|
62
|
+
[409 /* CONFLICT */]: {
|
|
63
|
+
type: "conflict",
|
|
64
|
+
title: "Conflict",
|
|
65
|
+
code: 409,
|
|
66
|
+
details: "The request conflicts with the current state of the resource."
|
|
67
|
+
},
|
|
68
|
+
[410 /* GONE */]: {
|
|
69
|
+
type: "gone",
|
|
70
|
+
title: "Gone",
|
|
71
|
+
code: 410,
|
|
72
|
+
details: "The requested resource is no longer available and will not be available again."
|
|
73
|
+
},
|
|
74
|
+
[411 /* LENGTH_REQUIRED */]: {
|
|
75
|
+
type: "length_required",
|
|
76
|
+
title: "Length Required",
|
|
77
|
+
code: 411,
|
|
78
|
+
details: "The request did not specify the length of its content, which is required."
|
|
79
|
+
},
|
|
80
|
+
[412 /* PRECONDITION_FAILED */]: {
|
|
81
|
+
type: "precondition_failed",
|
|
82
|
+
title: "Precondition Failed",
|
|
83
|
+
code: 412,
|
|
84
|
+
details: "One or more conditions in the request header fields evaluated to false."
|
|
85
|
+
},
|
|
86
|
+
[413 /* PAYLOAD_TOO_LARGE */]: {
|
|
87
|
+
type: "payload_too_large",
|
|
88
|
+
title: "Payload Too Large",
|
|
89
|
+
code: 413,
|
|
90
|
+
details: "The request payload is larger than the server is willing to process."
|
|
91
|
+
},
|
|
92
|
+
[414 /* URI_TOO_LONG */]: {
|
|
93
|
+
type: "uri_too_long",
|
|
94
|
+
title: "URI Too Long",
|
|
95
|
+
code: 414,
|
|
96
|
+
details: "The URI provided was too long for the server to process."
|
|
97
|
+
},
|
|
98
|
+
[415 /* UNSUPPORTED_MEDIA_TYPE */]: {
|
|
99
|
+
type: "unsupported_media_type",
|
|
100
|
+
title: "Unsupported Media Type",
|
|
101
|
+
code: 415,
|
|
102
|
+
details: "The media format of the requested data is not supported by the server."
|
|
103
|
+
},
|
|
104
|
+
[416 /* RANGE_NOT_SATISFIABLE */]: {
|
|
105
|
+
type: "range_not_satisfiable",
|
|
106
|
+
title: "Range Not Satisfiable",
|
|
107
|
+
code: 416,
|
|
108
|
+
details: "The range specified in the request header cannot be fulfilled."
|
|
109
|
+
},
|
|
110
|
+
[417 /* EXPECTATION_FAILED */]: {
|
|
111
|
+
type: "expectation_failed",
|
|
112
|
+
title: "Expectation Failed",
|
|
113
|
+
code: 417,
|
|
114
|
+
details: "The server cannot meet the requirements of the Expect request-header field."
|
|
115
|
+
},
|
|
116
|
+
[418 /* IM_A_TEAPOT */]: {
|
|
117
|
+
type: "im_a_teapot",
|
|
118
|
+
title: "I'm a Teapot",
|
|
119
|
+
code: 418,
|
|
120
|
+
details: "The server refuses to brew coffee because it is, permanently, a teapot."
|
|
121
|
+
},
|
|
122
|
+
[421 /* MISDIRECTED_REQUEST */]: {
|
|
123
|
+
type: "misdirected_request",
|
|
124
|
+
title: "Misdirected Request",
|
|
125
|
+
code: 421,
|
|
126
|
+
details: "The request was directed at a server that is not able to produce a response."
|
|
127
|
+
},
|
|
128
|
+
[422 /* UNPROCESSABLE_ENTITY */]: {
|
|
129
|
+
type: "unprocessable_entity",
|
|
130
|
+
title: "Unprocessable Entity",
|
|
131
|
+
code: 422,
|
|
132
|
+
details: "The request was well-formed but was unable to be followed due to semantic errors."
|
|
133
|
+
},
|
|
134
|
+
[423 /* LOCKED */]: {
|
|
135
|
+
type: "locked",
|
|
136
|
+
title: "Locked",
|
|
137
|
+
code: 423,
|
|
138
|
+
details: "The resource that is being accessed is locked."
|
|
139
|
+
},
|
|
140
|
+
[424 /* FAILED_DEPENDENCY */]: {
|
|
141
|
+
type: "failed_dependency",
|
|
142
|
+
title: "Failed Dependency",
|
|
143
|
+
code: 424,
|
|
144
|
+
details: "The request failed because it depended on another request that failed."
|
|
145
|
+
},
|
|
146
|
+
[425 /* TOO_EARLY */]: {
|
|
147
|
+
type: "too_early",
|
|
148
|
+
title: "Too Early",
|
|
149
|
+
code: 425,
|
|
150
|
+
details: "The server is unwilling to risk processing a request that might be replayed."
|
|
151
|
+
},
|
|
152
|
+
[426 /* UPGRADE_REQUIRED */]: {
|
|
153
|
+
type: "upgrade_required",
|
|
154
|
+
title: "Upgrade Required",
|
|
155
|
+
code: 426,
|
|
156
|
+
details: "The client should switch to a different protocol."
|
|
157
|
+
},
|
|
158
|
+
[428 /* PRECONDITION_REQUIRED */]: {
|
|
159
|
+
type: "precondition_required",
|
|
160
|
+
title: "Precondition Required",
|
|
161
|
+
code: 428,
|
|
162
|
+
details: "The origin server requires the request to be conditional."
|
|
163
|
+
},
|
|
164
|
+
[429 /* TOO_MANY_REQUESTS */]: {
|
|
165
|
+
type: "too_many_requests",
|
|
166
|
+
title: "Too Many Requests",
|
|
167
|
+
code: 429,
|
|
168
|
+
details: "The user has sent too many requests in a given amount of time.",
|
|
169
|
+
retryAfter: 60
|
|
170
|
+
},
|
|
171
|
+
[431 /* REQUEST_HEADER_FIELDS_TOO_LARGE */]: {
|
|
172
|
+
type: "request_header_fields_too_large",
|
|
173
|
+
title: "Request Header Fields Too Large",
|
|
174
|
+
code: 431,
|
|
175
|
+
details: "The server is unwilling to process the request because its header fields are too large."
|
|
176
|
+
},
|
|
177
|
+
[451 /* UNAVAILABLE_FOR_LEGAL_REASONS */]: {
|
|
178
|
+
type: "unavailable_for_legal_reasons",
|
|
179
|
+
title: "Unavailable For Legal Reasons",
|
|
180
|
+
code: 451,
|
|
181
|
+
details: "The resource is unavailable due to legal demands."
|
|
182
|
+
},
|
|
183
|
+
// ========================================================================
|
|
184
|
+
// 5xx Server Errors
|
|
185
|
+
// ========================================================================
|
|
186
|
+
[500 /* INTERNAL_SERVER_ERROR */]: {
|
|
187
|
+
type: "internal_server_error",
|
|
188
|
+
title: "Internal Server Error",
|
|
189
|
+
code: 500,
|
|
190
|
+
details: "An unexpected error occurred on the server."
|
|
191
|
+
},
|
|
192
|
+
[501 /* NOT_IMPLEMENTED */]: {
|
|
193
|
+
type: "not_implemented",
|
|
194
|
+
title: "Not Implemented",
|
|
195
|
+
code: 501,
|
|
196
|
+
details: "The server does not support the functionality required to fulfill the request."
|
|
197
|
+
},
|
|
198
|
+
[502 /* BAD_GATEWAY */]: {
|
|
199
|
+
type: "bad_gateway",
|
|
200
|
+
title: "Bad Gateway",
|
|
201
|
+
code: 502,
|
|
202
|
+
details: "The server received an invalid response from an upstream server."
|
|
203
|
+
},
|
|
204
|
+
[503 /* SERVICE_UNAVAILABLE */]: {
|
|
205
|
+
type: "service_unavailable",
|
|
206
|
+
title: "Service Unavailable",
|
|
207
|
+
code: 503,
|
|
208
|
+
details: "The server is currently unable to handle the request due to temporary overloading or maintenance.",
|
|
209
|
+
retryAfter: 60,
|
|
210
|
+
resolution: "Try again after a short period or contact support."
|
|
211
|
+
},
|
|
212
|
+
[504 /* GATEWAY_TIMEOUT */]: {
|
|
213
|
+
type: "gateway_timeout",
|
|
214
|
+
title: "Gateway Timeout",
|
|
215
|
+
code: 504,
|
|
216
|
+
details: "The server did not receive a timely response from an upstream server."
|
|
217
|
+
},
|
|
218
|
+
[505 /* HTTP_VERSION_NOT_SUPPORTED */]: {
|
|
219
|
+
type: "http_version_not_supported",
|
|
220
|
+
title: "HTTP Version Not Supported",
|
|
221
|
+
code: 505,
|
|
222
|
+
details: "The server does not support the HTTP protocol version used in the request."
|
|
223
|
+
},
|
|
224
|
+
[506 /* VARIANT_ALSO_NEGOTIATES */]: {
|
|
225
|
+
type: "variant_also_negotiates",
|
|
226
|
+
title: "Variant Also Negotiates",
|
|
227
|
+
code: 506,
|
|
228
|
+
details: "The server has an internal configuration error during content negotiation."
|
|
229
|
+
},
|
|
230
|
+
[507 /* INSUFFICIENT_STORAGE */]: {
|
|
231
|
+
type: "insufficient_storage",
|
|
232
|
+
title: "Insufficient Storage",
|
|
233
|
+
code: 507,
|
|
234
|
+
details: "The server is unable to store the representation needed to complete the request."
|
|
235
|
+
},
|
|
236
|
+
[508 /* LOOP_DETECTED */]: {
|
|
237
|
+
type: "loop_detected",
|
|
238
|
+
title: "Loop Detected",
|
|
239
|
+
code: 508,
|
|
240
|
+
details: "The server detected an infinite loop while processing the request."
|
|
241
|
+
},
|
|
242
|
+
[509 /* BANDWIDTH_LIMIT_EXCEEDED */]: {
|
|
243
|
+
type: "bandwidth_limit_exceeded",
|
|
244
|
+
title: "Bandwidth Limit Exceeded",
|
|
245
|
+
code: 509,
|
|
246
|
+
details: "The server has exceeded the bandwidth limit."
|
|
247
|
+
},
|
|
248
|
+
[510 /* NOT_EXTENDED */]: {
|
|
249
|
+
type: "not_extended",
|
|
250
|
+
title: "Not Extended",
|
|
251
|
+
code: 510,
|
|
252
|
+
details: "Further extensions to the request are required for the server to fulfill it."
|
|
253
|
+
},
|
|
254
|
+
[511 /* NETWORK_AUTHENTICATION_REQUIRED */]: {
|
|
255
|
+
type: "network_authentication_required",
|
|
256
|
+
title: "Network Authentication Required",
|
|
257
|
+
code: 511,
|
|
258
|
+
details: "The client needs to authenticate to gain network access."
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
Object.freeze(HttpErrorDefinitions);
|
|
262
|
+
function getErrorDefinition(code) {
|
|
263
|
+
if (code < 400 || code > 599) {
|
|
264
|
+
throw new RangeError(
|
|
265
|
+
`Invalid HTTP error code: ${code}. Must be between 400 and 599.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (HttpErrorDefinitions[code]) {
|
|
269
|
+
return HttpErrorDefinitions[code];
|
|
270
|
+
}
|
|
271
|
+
const isClientError = code >= 400 && code < 500;
|
|
272
|
+
return {
|
|
273
|
+
type: isClientError ? "unknown_client_error" : "unknown_server_error",
|
|
274
|
+
title: isClientError ? "Unknown Client Error" : "Unknown Server Error",
|
|
275
|
+
code,
|
|
276
|
+
details: isClientError ? "An unknown client error occurred." : "An unknown server error occurred."
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/errors/system-errors.ts
|
|
281
|
+
var SystemErrorStatusMap = Object.freeze({
|
|
282
|
+
// Connection-level failures -> 502 Bad Gateway
|
|
283
|
+
ECONNREFUSED: 502,
|
|
284
|
+
ECONNRESET: 502,
|
|
285
|
+
ECONNABORTED: 502,
|
|
286
|
+
EPIPE: 502,
|
|
287
|
+
EHOSTUNREACH: 502,
|
|
288
|
+
ENETUNREACH: 502,
|
|
289
|
+
ENETDOWN: 502,
|
|
290
|
+
ENOTFOUND: 502,
|
|
291
|
+
// DNS: host does not resolve
|
|
292
|
+
EPROTO: 502,
|
|
293
|
+
// TLS/protocol failure
|
|
294
|
+
ERR_TLS_CERT_ALTNAME_INVALID: 502,
|
|
295
|
+
// Timeouts -> 504 Gateway Timeout
|
|
296
|
+
ETIMEDOUT: 504,
|
|
297
|
+
ESOCKETTIMEDOUT: 504,
|
|
298
|
+
// Temporary conditions -> 503 Service Unavailable
|
|
299
|
+
EAI_AGAIN: 503,
|
|
300
|
+
// DNS: temporary resolution failure
|
|
301
|
+
EMFILE: 503,
|
|
302
|
+
// too many open files (fd exhaustion)
|
|
303
|
+
ENFILE: 503,
|
|
304
|
+
ENOBUFS: 503,
|
|
305
|
+
// undici / Node 18+ global fetch
|
|
306
|
+
UND_ERR_CONNECT_TIMEOUT: 504,
|
|
307
|
+
UND_ERR_HEADERS_TIMEOUT: 504,
|
|
308
|
+
UND_ERR_BODY_TIMEOUT: 504,
|
|
309
|
+
UND_ERR_SOCKET: 502,
|
|
310
|
+
UND_ERR_DESTROYED: 502,
|
|
311
|
+
UND_ERR_CLOSED: 502
|
|
312
|
+
});
|
|
313
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
314
|
+
function findSystemErrorCode(error) {
|
|
315
|
+
let current = error;
|
|
316
|
+
let depth = 0;
|
|
317
|
+
while (current !== null && typeof current === "object" && depth < MAX_CAUSE_DEPTH) {
|
|
318
|
+
const code = current.code;
|
|
319
|
+
if (typeof code === "string" && code in SystemErrorStatusMap) {
|
|
320
|
+
return code;
|
|
321
|
+
}
|
|
322
|
+
current = current.cause;
|
|
323
|
+
depth++;
|
|
324
|
+
}
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
function mapSystemError(error) {
|
|
328
|
+
const code = findSystemErrorCode(error);
|
|
329
|
+
if (code === void 0) {
|
|
330
|
+
return void 0;
|
|
331
|
+
}
|
|
332
|
+
const status = SystemErrorStatusMap[code];
|
|
333
|
+
return new HttpError(status, {
|
|
334
|
+
message: error instanceof Error ? error.message : String(error),
|
|
335
|
+
cause: error instanceof Error ? error : void 0,
|
|
336
|
+
expose: false,
|
|
337
|
+
errorCode: code
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/errors/HttpError.ts
|
|
342
|
+
var HttpError = class _HttpError extends Error {
|
|
343
|
+
/** HTTP status code */
|
|
344
|
+
code;
|
|
345
|
+
/** Lowercase error type identifier */
|
|
346
|
+
type;
|
|
347
|
+
/** Human-readable error title */
|
|
348
|
+
title;
|
|
349
|
+
/** Default error description from definition */
|
|
350
|
+
details;
|
|
351
|
+
/** Additional error metadata */
|
|
352
|
+
metadata;
|
|
353
|
+
/** Original error cause (also available as native `Error.cause`) */
|
|
354
|
+
cause;
|
|
355
|
+
/** Retry-after time in seconds (if applicable) */
|
|
356
|
+
retryAfter;
|
|
357
|
+
/** Whether `message` is safe to send to clients (4xx: true, 5xx: false by default) */
|
|
358
|
+
expose;
|
|
359
|
+
/** Stable application-level error code (e.g. "USER_NOT_FOUND") */
|
|
360
|
+
errorCode;
|
|
361
|
+
/** Structured validation issues */
|
|
362
|
+
validationErrors;
|
|
363
|
+
/** Extra HTTP headers associated with this error */
|
|
364
|
+
headers;
|
|
365
|
+
/** RFC 9457 `instance` URI for this specific occurrence */
|
|
366
|
+
instance;
|
|
367
|
+
/**
|
|
368
|
+
* Creates a new HttpError instance
|
|
369
|
+
*
|
|
370
|
+
* @param code - HTTP status code (e.g., 404, 500)
|
|
371
|
+
* @param options - Optional configuration
|
|
372
|
+
*/
|
|
373
|
+
constructor(code, options = {}) {
|
|
374
|
+
const errorInfo = getErrorDefinition(code);
|
|
375
|
+
const finalMessage = options.message ?? errorInfo.details;
|
|
376
|
+
super(finalMessage);
|
|
377
|
+
this.name = "HttpError";
|
|
378
|
+
this.code = errorInfo.code;
|
|
379
|
+
this.type = errorInfo.type;
|
|
380
|
+
this.title = errorInfo.title;
|
|
381
|
+
this.details = errorInfo.details;
|
|
382
|
+
this.metadata = options.metadata;
|
|
383
|
+
this.retryAfter = options.retryAfter ?? errorInfo.retryAfter;
|
|
384
|
+
this.expose = options.expose ?? errorInfo.code < 500;
|
|
385
|
+
this.errorCode = options.errorCode;
|
|
386
|
+
this.validationErrors = options.validationErrors;
|
|
387
|
+
this.headers = options.headers;
|
|
388
|
+
this.instance = options.instance;
|
|
389
|
+
if (options.cause !== void 0) {
|
|
390
|
+
this.cause = options.cause;
|
|
391
|
+
}
|
|
392
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
393
|
+
Error.captureStackTrace(this, _HttpError);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* The message that is safe to send to clients.
|
|
398
|
+
* Falls back to the generic definition message when `expose` is false.
|
|
399
|
+
*/
|
|
400
|
+
get safeMessage() {
|
|
401
|
+
return this.expose ? this.message : this.details;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* HTTP headers that should accompany this error response
|
|
405
|
+
* (e.g. `Retry-After` for 429/503, plus any custom headers).
|
|
406
|
+
*/
|
|
407
|
+
getHeaders() {
|
|
408
|
+
const headers = { ...this.headers };
|
|
409
|
+
if (this.retryAfter !== void 0) {
|
|
410
|
+
headers["Retry-After"] = String(this.retryAfter);
|
|
411
|
+
}
|
|
412
|
+
return headers;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Convert error to plain object for JSON serialization.
|
|
416
|
+
* Note: contains the full (non-sanitized) message - intended for logging.
|
|
417
|
+
* Use `kit.error()` / `toProblemDetails()` for client output.
|
|
418
|
+
*/
|
|
419
|
+
toJSON() {
|
|
420
|
+
return {
|
|
421
|
+
name: this.name,
|
|
422
|
+
code: this.code,
|
|
423
|
+
type: this.type,
|
|
424
|
+
title: this.title,
|
|
425
|
+
message: this.message,
|
|
426
|
+
details: this.details,
|
|
427
|
+
errorCode: this.errorCode,
|
|
428
|
+
metadata: this.metadata,
|
|
429
|
+
retryAfter: this.retryAfter,
|
|
430
|
+
expose: this.expose,
|
|
431
|
+
validationErrors: this.validationErrors
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Build an RFC 9457 Problem Details object for this error.
|
|
436
|
+
* Serve it with `Content-Type: application/problem+json`.
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* const problem = HttpError.notFound('User not found').toProblemDetails({
|
|
441
|
+
* typeBase: 'https://errors.example.com',
|
|
442
|
+
* instance: '/users/42',
|
|
443
|
+
* });
|
|
444
|
+
* // { type: 'https://errors.example.com/not_found', title: 'Not Found',
|
|
445
|
+
* // status: 404, detail: 'User not found', instance: '/users/42' }
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
toProblemDetails(options = {}) {
|
|
449
|
+
const exposed = options.expose ?? this.expose;
|
|
450
|
+
const problem = {
|
|
451
|
+
type: options.typeBase ? `${options.typeBase.replace(/\/$/, "")}/${this.type}` : "about:blank",
|
|
452
|
+
title: this.title,
|
|
453
|
+
status: this.code,
|
|
454
|
+
detail: exposed ? this.message : this.details
|
|
455
|
+
};
|
|
456
|
+
if (options.instance ?? this.instance) {
|
|
457
|
+
problem.instance = options.instance ?? this.instance;
|
|
458
|
+
}
|
|
459
|
+
if (this.errorCode) {
|
|
460
|
+
problem.code = this.errorCode;
|
|
461
|
+
}
|
|
462
|
+
if (this.validationErrors?.length) {
|
|
463
|
+
problem.errors = this.validationErrors;
|
|
464
|
+
}
|
|
465
|
+
if (options.requestId) {
|
|
466
|
+
problem.request_id = options.requestId;
|
|
467
|
+
}
|
|
468
|
+
if (options.extensions) {
|
|
469
|
+
for (const [key, value] of Object.entries(options.extensions)) {
|
|
470
|
+
if (!(key in problem)) {
|
|
471
|
+
problem[key] = value;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return problem;
|
|
476
|
+
}
|
|
477
|
+
// ========================================================================
|
|
478
|
+
// Factory Methods - 4xx Client Errors
|
|
479
|
+
// ========================================================================
|
|
480
|
+
/** 400 Bad Request */
|
|
481
|
+
static badRequest(message, metadata) {
|
|
482
|
+
return new _HttpError(400, { message, metadata });
|
|
483
|
+
}
|
|
484
|
+
/** 401 Unauthorized */
|
|
485
|
+
static unauthorized(message, metadata) {
|
|
486
|
+
return new _HttpError(401, { message, metadata });
|
|
487
|
+
}
|
|
488
|
+
/** 402 Payment Required */
|
|
489
|
+
static paymentRequired(message, metadata) {
|
|
490
|
+
return new _HttpError(402, { message, metadata });
|
|
491
|
+
}
|
|
492
|
+
/** 403 Forbidden */
|
|
493
|
+
static forbidden(message, metadata) {
|
|
494
|
+
return new _HttpError(403, { message, metadata });
|
|
495
|
+
}
|
|
496
|
+
/** 404 Not Found */
|
|
497
|
+
static notFound(message, metadata) {
|
|
498
|
+
return new _HttpError(404, { message, metadata });
|
|
499
|
+
}
|
|
500
|
+
/** 405 Method Not Allowed */
|
|
501
|
+
static methodNotAllowed(message, metadata) {
|
|
502
|
+
return new _HttpError(405, { message, metadata });
|
|
503
|
+
}
|
|
504
|
+
/** 406 Not Acceptable */
|
|
505
|
+
static notAcceptable(message, metadata) {
|
|
506
|
+
return new _HttpError(406, { message, metadata });
|
|
507
|
+
}
|
|
508
|
+
/** 407 Proxy Authentication Required */
|
|
509
|
+
static proxyAuthenticationRequired(message, metadata) {
|
|
510
|
+
return new _HttpError(407, { message, metadata });
|
|
511
|
+
}
|
|
512
|
+
/** 408 Request Timeout */
|
|
513
|
+
static requestTimeout(message, metadata) {
|
|
514
|
+
return new _HttpError(408, { message, metadata });
|
|
515
|
+
}
|
|
516
|
+
/** 409 Conflict */
|
|
517
|
+
static conflict(message, metadata) {
|
|
518
|
+
return new _HttpError(409, { message, metadata });
|
|
519
|
+
}
|
|
520
|
+
/** 410 Gone */
|
|
521
|
+
static gone(message, metadata) {
|
|
522
|
+
return new _HttpError(410, { message, metadata });
|
|
523
|
+
}
|
|
524
|
+
/** 411 Length Required */
|
|
525
|
+
static lengthRequired(message, metadata) {
|
|
526
|
+
return new _HttpError(411, { message, metadata });
|
|
527
|
+
}
|
|
528
|
+
/** 412 Precondition Failed */
|
|
529
|
+
static preconditionFailed(message, metadata) {
|
|
530
|
+
return new _HttpError(412, { message, metadata });
|
|
531
|
+
}
|
|
532
|
+
/** 413 Payload Too Large */
|
|
533
|
+
static payloadTooLarge(message, metadata) {
|
|
534
|
+
return new _HttpError(413, { message, metadata });
|
|
535
|
+
}
|
|
536
|
+
/** 414 URI Too Long */
|
|
537
|
+
static uriTooLong(message, metadata) {
|
|
538
|
+
return new _HttpError(414, { message, metadata });
|
|
539
|
+
}
|
|
540
|
+
/** 415 Unsupported Media Type */
|
|
541
|
+
static unsupportedMediaType(message, metadata) {
|
|
542
|
+
return new _HttpError(415, { message, metadata });
|
|
543
|
+
}
|
|
544
|
+
/** 416 Range Not Satisfiable */
|
|
545
|
+
static rangeNotSatisfiable(message, metadata) {
|
|
546
|
+
return new _HttpError(416, { message, metadata });
|
|
547
|
+
}
|
|
548
|
+
/** 417 Expectation Failed */
|
|
549
|
+
static expectationFailed(message, metadata) {
|
|
550
|
+
return new _HttpError(417, { message, metadata });
|
|
551
|
+
}
|
|
552
|
+
/** 418 I'm a Teapot */
|
|
553
|
+
static imATeapot(message, metadata) {
|
|
554
|
+
return new _HttpError(418, { message, metadata });
|
|
555
|
+
}
|
|
556
|
+
/** 421 Misdirected Request */
|
|
557
|
+
static misdirectedRequest(message, metadata) {
|
|
558
|
+
return new _HttpError(421, { message, metadata });
|
|
559
|
+
}
|
|
560
|
+
/** 422 Unprocessable Entity */
|
|
561
|
+
static unprocessableEntity(message, metadata) {
|
|
562
|
+
return new _HttpError(422, { message, metadata });
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* 422 Unprocessable Entity with structured validation issues.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* ```ts
|
|
569
|
+
* throw HttpError.validation([
|
|
570
|
+
* { field: 'email', message: 'Invalid email format', code: 'invalid_format' },
|
|
571
|
+
* { field: 'age', message: 'Must be >= 18', code: 'too_small' },
|
|
572
|
+
* ]);
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
static validation(errors, message = "Validation failed", code = 422) {
|
|
576
|
+
return new _HttpError(code, { message, validationErrors: errors, errorCode: "VALIDATION_FAILED" });
|
|
577
|
+
}
|
|
578
|
+
/** 423 Locked */
|
|
579
|
+
static locked(message, metadata) {
|
|
580
|
+
return new _HttpError(423, { message, metadata });
|
|
581
|
+
}
|
|
582
|
+
/** 424 Failed Dependency */
|
|
583
|
+
static failedDependency(message, metadata) {
|
|
584
|
+
return new _HttpError(424, { message, metadata });
|
|
585
|
+
}
|
|
586
|
+
/** 425 Too Early */
|
|
587
|
+
static tooEarly(message, metadata) {
|
|
588
|
+
return new _HttpError(425, { message, metadata });
|
|
589
|
+
}
|
|
590
|
+
/** 426 Upgrade Required */
|
|
591
|
+
static upgradeRequired(message, metadata) {
|
|
592
|
+
return new _HttpError(426, { message, metadata });
|
|
593
|
+
}
|
|
594
|
+
/** 428 Precondition Required */
|
|
595
|
+
static preconditionRequired(message, metadata) {
|
|
596
|
+
return new _HttpError(428, { message, metadata });
|
|
597
|
+
}
|
|
598
|
+
/** 429 Too Many Requests */
|
|
599
|
+
static tooManyRequests(message, retryAfter, metadata) {
|
|
600
|
+
return new _HttpError(429, { message, metadata, retryAfter });
|
|
601
|
+
}
|
|
602
|
+
/** 431 Request Header Fields Too Large */
|
|
603
|
+
static requestHeaderFieldsTooLarge(message, metadata) {
|
|
604
|
+
return new _HttpError(431, { message, metadata });
|
|
605
|
+
}
|
|
606
|
+
/** 451 Unavailable For Legal Reasons */
|
|
607
|
+
static unavailableForLegalReasons(message, metadata) {
|
|
608
|
+
return new _HttpError(451, { message, metadata });
|
|
609
|
+
}
|
|
610
|
+
// ========================================================================
|
|
611
|
+
// Factory Methods - 5xx Server Errors
|
|
612
|
+
// ========================================================================
|
|
613
|
+
/** 500 Internal Server Error */
|
|
614
|
+
static internalServerError(message, metadata) {
|
|
615
|
+
return new _HttpError(500, { message, metadata });
|
|
616
|
+
}
|
|
617
|
+
/** 501 Not Implemented */
|
|
618
|
+
static notImplemented(message, metadata) {
|
|
619
|
+
return new _HttpError(501, { message, metadata });
|
|
620
|
+
}
|
|
621
|
+
/** 502 Bad Gateway */
|
|
622
|
+
static badGateway(message, metadata) {
|
|
623
|
+
return new _HttpError(502, { message, metadata });
|
|
624
|
+
}
|
|
625
|
+
/** 503 Service Unavailable */
|
|
626
|
+
static serviceUnavailable(message, retryAfter, metadata) {
|
|
627
|
+
return new _HttpError(503, { message, metadata, retryAfter });
|
|
628
|
+
}
|
|
629
|
+
/** 504 Gateway Timeout */
|
|
630
|
+
static gatewayTimeout(message, metadata) {
|
|
631
|
+
return new _HttpError(504, { message, metadata });
|
|
632
|
+
}
|
|
633
|
+
/** 505 HTTP Version Not Supported */
|
|
634
|
+
static httpVersionNotSupported(message, metadata) {
|
|
635
|
+
return new _HttpError(505, { message, metadata });
|
|
636
|
+
}
|
|
637
|
+
/** 506 Variant Also Negotiates */
|
|
638
|
+
static variantAlsoNegotiates(message, metadata) {
|
|
639
|
+
return new _HttpError(506, { message, metadata });
|
|
640
|
+
}
|
|
641
|
+
/** 507 Insufficient Storage */
|
|
642
|
+
static insufficientStorage(message, metadata) {
|
|
643
|
+
return new _HttpError(507, { message, metadata });
|
|
644
|
+
}
|
|
645
|
+
/** 508 Loop Detected */
|
|
646
|
+
static loopDetected(message, metadata) {
|
|
647
|
+
return new _HttpError(508, { message, metadata });
|
|
648
|
+
}
|
|
649
|
+
/** 509 Bandwidth Limit Exceeded */
|
|
650
|
+
static bandwidthLimitExceeded(message, metadata) {
|
|
651
|
+
return new _HttpError(509, { message, metadata });
|
|
652
|
+
}
|
|
653
|
+
/** 510 Not Extended */
|
|
654
|
+
static notExtended(message, metadata) {
|
|
655
|
+
return new _HttpError(510, { message, metadata });
|
|
656
|
+
}
|
|
657
|
+
/** 511 Network Authentication Required */
|
|
658
|
+
static networkAuthenticationRequired(message, metadata) {
|
|
659
|
+
return new _HttpError(511, { message, metadata });
|
|
660
|
+
}
|
|
661
|
+
// ========================================================================
|
|
662
|
+
// Utility Methods
|
|
663
|
+
// ========================================================================
|
|
664
|
+
/**
|
|
665
|
+
* Create an HttpError from an unknown error.
|
|
666
|
+
*
|
|
667
|
+
* Security: wrapped errors are marked `expose: false` when the resulting
|
|
668
|
+
* status is 5xx, so unexpected internal messages (DB errors, stack info,
|
|
669
|
+
* connection strings...) are never sent to clients. The original message
|
|
670
|
+
* and cause are preserved on the instance for logging.
|
|
671
|
+
*
|
|
672
|
+
* System/socket errors (ECONNREFUSED, ETIMEDOUT, undici fetch codes...)
|
|
673
|
+
* are automatically mapped to the semantically correct 502/503/504 -
|
|
674
|
+
* including codes buried in the `cause` chain (e.g. Node's
|
|
675
|
+
* `fetch failed` TypeError) - with the syscall code as `errorCode`.
|
|
676
|
+
*/
|
|
677
|
+
static fromError(error, fallbackCode = 500) {
|
|
678
|
+
if (error instanceof _HttpError) {
|
|
679
|
+
return error;
|
|
680
|
+
}
|
|
681
|
+
if (fallbackCode < 400 || fallbackCode > 599) {
|
|
682
|
+
throw new RangeError(
|
|
683
|
+
`fallbackCode must be between 400 and 599, got ${fallbackCode}`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
const systemError = mapSystemError(error);
|
|
687
|
+
if (systemError !== void 0) {
|
|
688
|
+
return systemError;
|
|
689
|
+
}
|
|
690
|
+
if (error instanceof Error) {
|
|
691
|
+
return new _HttpError(fallbackCode, {
|
|
692
|
+
message: error.message,
|
|
693
|
+
cause: error,
|
|
694
|
+
expose: fallbackCode < 500
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
return new _HttpError(fallbackCode, {
|
|
698
|
+
message: String(error),
|
|
699
|
+
expose: fallbackCode < 500
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Check if an error is an HttpError
|
|
704
|
+
*/
|
|
705
|
+
static isHttpError(error) {
|
|
706
|
+
return error instanceof _HttpError;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Check if error is a client error (4xx)
|
|
710
|
+
*/
|
|
711
|
+
isClientError() {
|
|
712
|
+
return this.code >= 400 && this.code < 500;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Check if error is a server error (5xx)
|
|
716
|
+
*/
|
|
717
|
+
isServerError() {
|
|
718
|
+
return this.code >= 500 && this.code < 600;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Flattened chain of cause messages (most recent first).
|
|
722
|
+
* Useful for structured logging; never serialized to clients in production.
|
|
723
|
+
*/
|
|
724
|
+
getCauseChain() {
|
|
725
|
+
const chain = [];
|
|
726
|
+
let current = this.cause;
|
|
727
|
+
let depth = 0;
|
|
728
|
+
while (current instanceof Error && depth < 10) {
|
|
729
|
+
chain.push(`${current.name}: ${current.message}`);
|
|
730
|
+
current = current.cause;
|
|
731
|
+
depth++;
|
|
732
|
+
}
|
|
733
|
+
return chain;
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// src/constants/success-definitions.ts
|
|
738
|
+
var HttpSuccessDefinitions = {
|
|
739
|
+
[200 /* OK */]: {
|
|
740
|
+
code: 200,
|
|
741
|
+
description: "The request has succeeded."
|
|
742
|
+
},
|
|
743
|
+
[201 /* CREATED */]: {
|
|
744
|
+
code: 201,
|
|
745
|
+
description: "The request has been fulfilled and resulted in a new resource being created."
|
|
746
|
+
},
|
|
747
|
+
[202 /* ACCEPTED */]: {
|
|
748
|
+
code: 202,
|
|
749
|
+
description: "The request has been accepted for processing, but the processing has not been completed."
|
|
750
|
+
},
|
|
751
|
+
[203 /* NON_AUTHORITATIVE_INFORMATION */]: {
|
|
752
|
+
code: 203,
|
|
753
|
+
description: "The returned meta-information is from a local or third-party copy."
|
|
754
|
+
},
|
|
755
|
+
[204 /* NO_CONTENT */]: {
|
|
756
|
+
code: 204,
|
|
757
|
+
description: "The server successfully processed the request and is not returning any content."
|
|
758
|
+
},
|
|
759
|
+
[205 /* RESET_CONTENT */]: {
|
|
760
|
+
code: 205,
|
|
761
|
+
description: "The server successfully processed the request and requires the requester to reset the document view."
|
|
762
|
+
},
|
|
763
|
+
[206 /* PARTIAL_CONTENT */]: {
|
|
764
|
+
code: 206,
|
|
765
|
+
description: "The server is delivering only part of the resource due to a range header sent by the client."
|
|
766
|
+
},
|
|
767
|
+
[207 /* MULTI_STATUS */]: {
|
|
768
|
+
code: 207,
|
|
769
|
+
description: "The message body contains multiple status codes for multiple independent operations."
|
|
770
|
+
},
|
|
771
|
+
[208 /* ALREADY_REPORTED */]: {
|
|
772
|
+
code: 208,
|
|
773
|
+
description: "The members of a DAV binding have already been enumerated in a preceding part of the response."
|
|
774
|
+
},
|
|
775
|
+
[226 /* IM_USED */]: {
|
|
776
|
+
code: 226,
|
|
777
|
+
description: "The server has fulfilled a GET request and the response is a representation of the result of one or more instance-manipulations."
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
Object.freeze(HttpSuccessDefinitions);
|
|
781
|
+
var HttpRedirectDefinitions = {
|
|
782
|
+
[300 /* MULTIPLE_CHOICES */]: {
|
|
783
|
+
code: 300,
|
|
784
|
+
description: "The request has more than one possible response."
|
|
785
|
+
},
|
|
786
|
+
[301 /* MOVED_PERMANENTLY */]: {
|
|
787
|
+
code: 301,
|
|
788
|
+
description: "The URL of the requested resource has been changed permanently."
|
|
789
|
+
},
|
|
790
|
+
[302 /* FOUND */]: {
|
|
791
|
+
code: 302,
|
|
792
|
+
description: "The URI of the requested resource has been changed temporarily."
|
|
793
|
+
},
|
|
794
|
+
[303 /* SEE_OTHER */]: {
|
|
795
|
+
code: 303,
|
|
796
|
+
description: "The response can be found under another URI using the GET method."
|
|
797
|
+
},
|
|
798
|
+
[304 /* NOT_MODIFIED */]: {
|
|
799
|
+
code: 304,
|
|
800
|
+
description: "The resource has not been modified since the version specified by the request headers."
|
|
801
|
+
},
|
|
802
|
+
[305 /* USE_PROXY */]: {
|
|
803
|
+
code: 305,
|
|
804
|
+
description: "The requested resource must be accessed through the proxy."
|
|
805
|
+
},
|
|
806
|
+
[307 /* TEMPORARY_REDIRECT */]: {
|
|
807
|
+
code: 307,
|
|
808
|
+
description: "The request should be repeated with another URI but future requests should use the original URI."
|
|
809
|
+
},
|
|
810
|
+
[308 /* PERMANENT_REDIRECT */]: {
|
|
811
|
+
code: 308,
|
|
812
|
+
description: "The request and all future requests should be repeated using another URI."
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
Object.freeze(HttpRedirectDefinitions);
|
|
816
|
+
var HttpInfoDefinitions = {
|
|
817
|
+
[100 /* CONTINUE */]: {
|
|
818
|
+
code: 100,
|
|
819
|
+
description: "The initial part of a request has been received and has not yet been rejected by the server."
|
|
820
|
+
},
|
|
821
|
+
[101 /* SWITCHING_PROTOCOLS */]: {
|
|
822
|
+
code: 101,
|
|
823
|
+
description: "The server is switching protocols as requested by the client."
|
|
824
|
+
},
|
|
825
|
+
[102 /* PROCESSING */]: {
|
|
826
|
+
code: 102,
|
|
827
|
+
description: "The server has received and is processing the request, but no response is available yet."
|
|
828
|
+
},
|
|
829
|
+
[103 /* EARLY_HINTS */]: {
|
|
830
|
+
code: 103,
|
|
831
|
+
description: "The server is sending some response headers before the final response."
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
Object.freeze(HttpInfoDefinitions);
|
|
835
|
+
function getSuccessDefinition(code) {
|
|
836
|
+
const definedInfo = HttpSuccessDefinitions[code] || HttpRedirectDefinitions[code] || HttpInfoDefinitions[code];
|
|
837
|
+
if (definedInfo) {
|
|
838
|
+
return definedInfo;
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
code,
|
|
842
|
+
description: "The request was processed with a non-standard status code."
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/kit.ts
|
|
847
|
+
function snakeToCamel(key) {
|
|
848
|
+
return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
849
|
+
}
|
|
850
|
+
function camelizeResponse(response) {
|
|
851
|
+
const out = {};
|
|
852
|
+
for (const [key, value] of Object.entries(response)) {
|
|
853
|
+
out[snakeToCamel(key)] = value;
|
|
854
|
+
}
|
|
855
|
+
const metadata = out.metadata;
|
|
856
|
+
if (metadata && typeof metadata === "object" && metadata.pagination && typeof metadata.pagination === "object") {
|
|
857
|
+
const pagination = {};
|
|
858
|
+
for (const [key, value] of Object.entries(metadata.pagination)) {
|
|
859
|
+
pagination[snakeToCamel(key)] = value;
|
|
860
|
+
}
|
|
861
|
+
out.metadata = { ...metadata, pagination };
|
|
862
|
+
}
|
|
863
|
+
return out;
|
|
864
|
+
}
|
|
865
|
+
var ResponseKit = class {
|
|
866
|
+
config;
|
|
867
|
+
constructor(config = {}) {
|
|
868
|
+
this.config = {
|
|
869
|
+
includeTimestamp: true,
|
|
870
|
+
format: "standard",
|
|
871
|
+
casing: "snake",
|
|
872
|
+
exposeServerErrors: false,
|
|
873
|
+
...config,
|
|
874
|
+
customMessages: { ...config.customMessages }
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
/** Merge partial configuration into this instance */
|
|
878
|
+
configure(config) {
|
|
879
|
+
Object.assign(this.config, {
|
|
880
|
+
...config,
|
|
881
|
+
customMessages: { ...this.config.customMessages, ...config.customMessages }
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
/** Snapshot of the current configuration */
|
|
885
|
+
getConfig() {
|
|
886
|
+
return { ...this.config };
|
|
887
|
+
}
|
|
888
|
+
/** Whether dev mode is active (stack traces, cause chains) */
|
|
889
|
+
isDevelopment() {
|
|
890
|
+
return this.config.isDevelopment ?? process.env.NODE_ENV === "development";
|
|
891
|
+
}
|
|
892
|
+
finalize(response) {
|
|
893
|
+
let out = this.config.casing === "camel" ? camelizeResponse(response) : response;
|
|
894
|
+
const transformer = this.config.responseTransformer;
|
|
895
|
+
if (transformer) {
|
|
896
|
+
out = transformer(out);
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Resolve the outgoing client-facing message for an error.
|
|
902
|
+
*
|
|
903
|
+
* Priority: messageResolver > explicit message (if exposed)
|
|
904
|
+
* > customMessages[code] > generic status description.
|
|
905
|
+
*/
|
|
906
|
+
resolveMessage(error, exposed) {
|
|
907
|
+
const resolved = this.config.messageResolver?.(error);
|
|
908
|
+
if (resolved !== void 0) return resolved;
|
|
909
|
+
const customDefault = this.config.customMessages?.[error.code];
|
|
910
|
+
const hasExplicitMessage = error.message !== error.details;
|
|
911
|
+
if (exposed && hasExplicitMessage) return error.message;
|
|
912
|
+
return customDefault ?? error.details;
|
|
913
|
+
}
|
|
914
|
+
// ========================================================================
|
|
915
|
+
// Success
|
|
916
|
+
// ========================================================================
|
|
917
|
+
/** Format a success response */
|
|
918
|
+
success(config = {}) {
|
|
919
|
+
const { data, message, statusCode = 200 /* OK */, metadata = {}, requestId } = config;
|
|
920
|
+
const successInfo = getSuccessDefinition(statusCode);
|
|
921
|
+
const response = {
|
|
922
|
+
success: true,
|
|
923
|
+
status_code: successInfo.code
|
|
924
|
+
};
|
|
925
|
+
if (this.config.includeTimestamp ?? true) {
|
|
926
|
+
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
927
|
+
}
|
|
928
|
+
if (requestId) {
|
|
929
|
+
response.request_id = requestId;
|
|
930
|
+
}
|
|
931
|
+
if (statusCode !== 204 /* NO_CONTENT */ && statusCode !== 205 /* RESET_CONTENT */ && statusCode !== 304 /* NOT_MODIFIED */) {
|
|
932
|
+
if (data !== null && data !== void 0) {
|
|
933
|
+
response.data = data;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (message) {
|
|
937
|
+
response.message = message;
|
|
938
|
+
}
|
|
939
|
+
if (Object.keys(metadata).length > 0) {
|
|
940
|
+
response.metadata = metadata;
|
|
941
|
+
}
|
|
942
|
+
return this.finalize(response);
|
|
943
|
+
}
|
|
944
|
+
// ========================================================================
|
|
945
|
+
// Error
|
|
946
|
+
// ========================================================================
|
|
947
|
+
/**
|
|
948
|
+
* Format an error response.
|
|
949
|
+
*
|
|
950
|
+
* Security: when the error is not exposable (5xx by default), the outgoing
|
|
951
|
+
* message falls back to the generic status description (or the configured
|
|
952
|
+
* custom message for that code) and `metadata` is omitted. The original
|
|
953
|
+
* message stays available on the error instance for logging.
|
|
954
|
+
*/
|
|
955
|
+
error(error, config = {}) {
|
|
956
|
+
const { includeStack, additionalFields, requestId } = config;
|
|
957
|
+
const cfg = this.config;
|
|
958
|
+
const exposed = config.expose ?? (error.expose || error.isServerError() && cfg.exposeServerErrors === true);
|
|
959
|
+
const response = {
|
|
960
|
+
success: false,
|
|
961
|
+
status_code: error.code,
|
|
962
|
+
error: {
|
|
963
|
+
type: error.type,
|
|
964
|
+
title: error.title,
|
|
965
|
+
message: this.resolveMessage(error, exposed)
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
if (cfg.includeTimestamp ?? true) {
|
|
969
|
+
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
970
|
+
}
|
|
971
|
+
if (requestId) {
|
|
972
|
+
response.request_id = requestId;
|
|
973
|
+
}
|
|
974
|
+
if (error.errorCode) {
|
|
975
|
+
response.error.code = error.errorCode;
|
|
976
|
+
}
|
|
977
|
+
if (error.details) {
|
|
978
|
+
response.error.details = error.details;
|
|
979
|
+
}
|
|
980
|
+
if (error.validationErrors?.length) {
|
|
981
|
+
response.error.errors = error.validationErrors;
|
|
982
|
+
}
|
|
983
|
+
if (includeStack ?? this.isDevelopment()) {
|
|
984
|
+
response.error.stack = error.stack;
|
|
985
|
+
const causes = error.getCauseChain();
|
|
986
|
+
if (causes.length > 0) {
|
|
987
|
+
response.error.causes = causes;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (error.retryAfter) {
|
|
991
|
+
response.retry_after = error.retryAfter;
|
|
992
|
+
}
|
|
993
|
+
if (error.metadata && exposed) {
|
|
994
|
+
const sanitized = cfg.metadataSanitizer ? cfg.metadataSanitizer(error.metadata) : error.metadata;
|
|
995
|
+
if (Object.keys(sanitized).length > 0) {
|
|
996
|
+
response.metadata = sanitized;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (additionalFields) {
|
|
1000
|
+
const { success, status_code, error: _error, timestamp, metadata, retry_after, request_id, ...safeFields } = additionalFields;
|
|
1001
|
+
Object.assign(response, safeFields);
|
|
1002
|
+
}
|
|
1003
|
+
return this.finalize(response);
|
|
1004
|
+
}
|
|
1005
|
+
/** Format a response from any error (converts to HttpError first) */
|
|
1006
|
+
fromError(error, config = {}) {
|
|
1007
|
+
const httpError = HttpError.fromError(error, config.fallbackCode);
|
|
1008
|
+
return this.error(httpError, config);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Build an RFC 9457 Problem Details body from any error.
|
|
1012
|
+
* Serve it with `Content-Type: application/problem+json`
|
|
1013
|
+
* (see `PROBLEM_CONTENT_TYPE`).
|
|
1014
|
+
*/
|
|
1015
|
+
problem(error, options = {}) {
|
|
1016
|
+
const httpError = HttpError.fromError(error);
|
|
1017
|
+
const exposed = options.expose ?? httpError.expose;
|
|
1018
|
+
const problem = httpError.toProblemDetails({
|
|
1019
|
+
typeBase: options.typeBase ?? this.config.problemTypeBase,
|
|
1020
|
+
...options
|
|
1021
|
+
});
|
|
1022
|
+
problem.detail = this.resolveMessage(httpError, exposed);
|
|
1023
|
+
return problem;
|
|
1024
|
+
}
|
|
1025
|
+
// ========================================================================
|
|
1026
|
+
// Convenience Methods
|
|
1027
|
+
// ========================================================================
|
|
1028
|
+
/** 200 OK */
|
|
1029
|
+
ok(data, message) {
|
|
1030
|
+
return this.success({ data, message, statusCode: 200 /* OK */ });
|
|
1031
|
+
}
|
|
1032
|
+
/** 201 Created */
|
|
1033
|
+
created(data, message) {
|
|
1034
|
+
return this.success({ data, message, statusCode: 201 /* CREATED */ });
|
|
1035
|
+
}
|
|
1036
|
+
/** 202 Accepted */
|
|
1037
|
+
accepted(data, message) {
|
|
1038
|
+
return this.success({ data, message, statusCode: 202 /* ACCEPTED */ });
|
|
1039
|
+
}
|
|
1040
|
+
/** 204 No Content */
|
|
1041
|
+
noContent() {
|
|
1042
|
+
return this.success({ statusCode: 204 /* NO_CONTENT */ });
|
|
1043
|
+
}
|
|
1044
|
+
/** 206 Partial Content */
|
|
1045
|
+
partialContent(data, message) {
|
|
1046
|
+
return this.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
|
|
1047
|
+
}
|
|
1048
|
+
/** 304 Not Modified */
|
|
1049
|
+
notModified() {
|
|
1050
|
+
return this.success({ statusCode: 304 /* NOT_MODIFIED */ });
|
|
1051
|
+
}
|
|
1052
|
+
// ========================================================================
|
|
1053
|
+
// Pagination
|
|
1054
|
+
// ========================================================================
|
|
1055
|
+
/** Create an offset-based paginated success response */
|
|
1056
|
+
paginated(data, pagination, message) {
|
|
1057
|
+
const effectiveLimit = pagination.limit > 0 ? pagination.limit : 1;
|
|
1058
|
+
const totalPages = pagination.totalPages ?? Math.ceil(pagination.total / effectiveLimit);
|
|
1059
|
+
return this.success({
|
|
1060
|
+
data,
|
|
1061
|
+
message,
|
|
1062
|
+
metadata: {
|
|
1063
|
+
pagination: {
|
|
1064
|
+
page: pagination.page,
|
|
1065
|
+
limit: effectiveLimit,
|
|
1066
|
+
total: pagination.total,
|
|
1067
|
+
total_pages: totalPages,
|
|
1068
|
+
has_next: pagination.page < totalPages,
|
|
1069
|
+
has_prev: pagination.page > 1
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create a cursor-based paginated success response.
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* ```ts
|
|
1079
|
+
* kit.paginatedCursor(items, { nextCursor: 'eyJpZCI6NDJ9', limit: 20 });
|
|
1080
|
+
* // metadata.pagination: { next_cursor, limit, has_next: true, has_prev: false }
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
paginatedCursor(data, cursor, message) {
|
|
1084
|
+
const pagination = {
|
|
1085
|
+
has_next: cursor.nextCursor !== void 0 && cursor.nextCursor !== null,
|
|
1086
|
+
has_prev: cursor.prevCursor !== void 0 && cursor.prevCursor !== null
|
|
1087
|
+
};
|
|
1088
|
+
if (cursor.nextCursor !== void 0) pagination.next_cursor = cursor.nextCursor;
|
|
1089
|
+
if (cursor.prevCursor !== void 0) pagination.prev_cursor = cursor.prevCursor;
|
|
1090
|
+
if (cursor.limit !== void 0) pagination.limit = cursor.limit;
|
|
1091
|
+
if (cursor.total !== void 0) pagination.total = cursor.total;
|
|
1092
|
+
return this.success({ data, message, metadata: { pagination } });
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
// src/responses/problem.ts
|
|
1097
|
+
var PROBLEM_CONTENT_TYPE = "application/problem+json";
|
|
1098
|
+
|
|
1099
|
+
// src/adapters/shared.ts
|
|
1100
|
+
var fallbackKit = new ResponseKit();
|
|
1101
|
+
function buildErrorPayload(err, requestId, options = {}) {
|
|
1102
|
+
const kit = options.kit ?? fallbackKit;
|
|
1103
|
+
const error = HttpError.fromError(err);
|
|
1104
|
+
const useProblem = options.problem ?? kit.getConfig().format === "problem";
|
|
1105
|
+
options.onError?.(error, requestId);
|
|
1106
|
+
const headers = error.getHeaders();
|
|
1107
|
+
if (useProblem) {
|
|
1108
|
+
return {
|
|
1109
|
+
status: error.code,
|
|
1110
|
+
headers,
|
|
1111
|
+
contentType: PROBLEM_CONTENT_TYPE,
|
|
1112
|
+
body: kit.problem(error, { requestId }),
|
|
1113
|
+
error
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
status: error.code,
|
|
1118
|
+
headers,
|
|
1119
|
+
contentType: "application/json; charset=utf-8",
|
|
1120
|
+
body: kit.error(error, { requestId, includeStack: options.includeStack }),
|
|
1121
|
+
error
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/adapters/hono.ts
|
|
1126
|
+
function honoErrorHandler(options = {}) {
|
|
1127
|
+
return (err, c) => {
|
|
1128
|
+
const requestId = c.req.header(options.requestIdHeader ?? "x-request-id");
|
|
1129
|
+
const payload = buildErrorPayload(err, requestId, options);
|
|
1130
|
+
return c.json(payload.body, payload.status, {
|
|
1131
|
+
"Content-Type": payload.contentType,
|
|
1132
|
+
...payload.headers
|
|
1133
|
+
});
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
exports.honoErrorHandler = honoErrorHandler;
|
|
1138
|
+
//# sourceMappingURL=hono.js.map
|
|
1139
|
+
//# sourceMappingURL=hono.js.map
|