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/index.js
CHANGED
|
@@ -1,45 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
HttpClientErrorCode: () => HttpClientErrorCode,
|
|
24
|
-
HttpError: () => HttpError,
|
|
25
|
-
HttpErrorCode: () => HttpErrorCode,
|
|
26
|
-
HttpErrorDefinitions: () => HttpErrorDefinitions,
|
|
27
|
-
HttpInfoCode: () => HttpInfoCode,
|
|
28
|
-
HttpInfoDefinitions: () => HttpInfoDefinitions,
|
|
29
|
-
HttpRedirectCode: () => HttpRedirectCode,
|
|
30
|
-
HttpRedirectDefinitions: () => HttpRedirectDefinitions,
|
|
31
|
-
HttpResponse: () => HttpResponse,
|
|
32
|
-
HttpServerErrorCode: () => HttpServerErrorCode,
|
|
33
|
-
HttpSuccessCode: () => HttpSuccessCode,
|
|
34
|
-
HttpSuccessDefinitions: () => HttpSuccessDefinitions,
|
|
35
|
-
configure: () => configure,
|
|
36
|
-
getConfig: () => getConfig,
|
|
37
|
-
getErrorDefinition: () => getErrorDefinition,
|
|
38
|
-
getSuccessDefinition: () => getSuccessDefinition,
|
|
39
|
-
isDevelopment: () => isDevelopment,
|
|
40
|
-
resetConfig: () => resetConfig
|
|
41
|
-
});
|
|
42
|
-
module.exports = __toCommonJS(index_exports);
|
|
1
|
+
'use strict';
|
|
43
2
|
|
|
44
3
|
// src/constants/status-codes.ts
|
|
45
4
|
var HttpClientErrorCode = /* @__PURE__ */ ((HttpClientErrorCode2) => {
|
|
@@ -383,8 +342,23 @@ var HttpErrorDefinitions = {
|
|
|
383
342
|
details: "The client needs to authenticate to gain network access."
|
|
384
343
|
}
|
|
385
344
|
};
|
|
345
|
+
Object.freeze(HttpErrorDefinitions);
|
|
386
346
|
function getErrorDefinition(code) {
|
|
387
|
-
|
|
347
|
+
if (code < 400 || code > 599) {
|
|
348
|
+
throw new RangeError(
|
|
349
|
+
`Invalid HTTP error code: ${code}. Must be between 400 and 599.`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (HttpErrorDefinitions[code]) {
|
|
353
|
+
return HttpErrorDefinitions[code];
|
|
354
|
+
}
|
|
355
|
+
const isClientError = code >= 400 && code < 500;
|
|
356
|
+
return {
|
|
357
|
+
type: isClientError ? "unknown_client_error" : "unknown_server_error",
|
|
358
|
+
title: isClientError ? "Unknown Client Error" : "Unknown Server Error",
|
|
359
|
+
code,
|
|
360
|
+
details: isClientError ? "An unknown client error occurred." : "An unknown server error occurred."
|
|
361
|
+
};
|
|
388
362
|
}
|
|
389
363
|
|
|
390
364
|
// src/constants/success-definitions.ts
|
|
@@ -430,6 +404,7 @@ var HttpSuccessDefinitions = {
|
|
|
430
404
|
description: "The server has fulfilled a GET request and the response is a representation of the result of one or more instance-manipulations."
|
|
431
405
|
}
|
|
432
406
|
};
|
|
407
|
+
Object.freeze(HttpSuccessDefinitions);
|
|
433
408
|
var HttpRedirectDefinitions = {
|
|
434
409
|
[300 /* MULTIPLE_CHOICES */]: {
|
|
435
410
|
code: 300,
|
|
@@ -464,6 +439,7 @@ var HttpRedirectDefinitions = {
|
|
|
464
439
|
description: "The request and all future requests should be repeated using another URI."
|
|
465
440
|
}
|
|
466
441
|
};
|
|
442
|
+
Object.freeze(HttpRedirectDefinitions);
|
|
467
443
|
var HttpInfoDefinitions = {
|
|
468
444
|
[100 /* CONTINUE */]: {
|
|
469
445
|
code: 100,
|
|
@@ -482,66 +458,156 @@ var HttpInfoDefinitions = {
|
|
|
482
458
|
description: "The server is sending some response headers before the final response."
|
|
483
459
|
}
|
|
484
460
|
};
|
|
461
|
+
Object.freeze(HttpInfoDefinitions);
|
|
485
462
|
function getSuccessDefinition(code) {
|
|
486
|
-
|
|
463
|
+
const definedInfo = HttpSuccessDefinitions[code] || HttpRedirectDefinitions[code] || HttpInfoDefinitions[code];
|
|
464
|
+
if (definedInfo) {
|
|
465
|
+
return definedInfo;
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
code,
|
|
469
|
+
description: "The request was processed with a non-standard status code."
|
|
470
|
+
};
|
|
487
471
|
}
|
|
488
472
|
|
|
489
|
-
// src/
|
|
490
|
-
var
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
473
|
+
// src/errors/system-errors.ts
|
|
474
|
+
var SystemErrorStatusMap = Object.freeze({
|
|
475
|
+
// Connection-level failures -> 502 Bad Gateway
|
|
476
|
+
ECONNREFUSED: 502,
|
|
477
|
+
ECONNRESET: 502,
|
|
478
|
+
ECONNABORTED: 502,
|
|
479
|
+
EPIPE: 502,
|
|
480
|
+
EHOSTUNREACH: 502,
|
|
481
|
+
ENETUNREACH: 502,
|
|
482
|
+
ENETDOWN: 502,
|
|
483
|
+
ENOTFOUND: 502,
|
|
484
|
+
// DNS: host does not resolve
|
|
485
|
+
EPROTO: 502,
|
|
486
|
+
// TLS/protocol failure
|
|
487
|
+
ERR_TLS_CERT_ALTNAME_INVALID: 502,
|
|
488
|
+
// Timeouts -> 504 Gateway Timeout
|
|
489
|
+
ETIMEDOUT: 504,
|
|
490
|
+
ESOCKETTIMEDOUT: 504,
|
|
491
|
+
// Temporary conditions -> 503 Service Unavailable
|
|
492
|
+
EAI_AGAIN: 503,
|
|
493
|
+
// DNS: temporary resolution failure
|
|
494
|
+
EMFILE: 503,
|
|
495
|
+
// too many open files (fd exhaustion)
|
|
496
|
+
ENFILE: 503,
|
|
497
|
+
ENOBUFS: 503,
|
|
498
|
+
// undici / Node 18+ global fetch
|
|
499
|
+
UND_ERR_CONNECT_TIMEOUT: 504,
|
|
500
|
+
UND_ERR_HEADERS_TIMEOUT: 504,
|
|
501
|
+
UND_ERR_BODY_TIMEOUT: 504,
|
|
502
|
+
UND_ERR_SOCKET: 502,
|
|
503
|
+
UND_ERR_DESTROYED: 502,
|
|
504
|
+
UND_ERR_CLOSED: 502
|
|
505
|
+
});
|
|
506
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
507
|
+
function findSystemErrorCode(error) {
|
|
508
|
+
let current = error;
|
|
509
|
+
let depth = 0;
|
|
510
|
+
while (current !== null && typeof current === "object" && depth < MAX_CAUSE_DEPTH) {
|
|
511
|
+
const code = current.code;
|
|
512
|
+
if (typeof code === "string" && code in SystemErrorStatusMap) {
|
|
513
|
+
return code;
|
|
514
|
+
}
|
|
515
|
+
current = current.cause;
|
|
516
|
+
depth++;
|
|
517
|
+
}
|
|
518
|
+
return void 0;
|
|
514
519
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
520
|
+
function mapSystemError(error) {
|
|
521
|
+
const code = findSystemErrorCode(error);
|
|
522
|
+
if (code === void 0) {
|
|
523
|
+
return void 0;
|
|
524
|
+
}
|
|
525
|
+
const status = SystemErrorStatusMap[code];
|
|
526
|
+
return new HttpError(status, {
|
|
527
|
+
message: error instanceof Error ? error.message : String(error),
|
|
528
|
+
cause: error instanceof Error ? error : void 0,
|
|
529
|
+
expose: false,
|
|
530
|
+
errorCode: code
|
|
531
|
+
});
|
|
517
532
|
}
|
|
518
533
|
|
|
519
534
|
// src/errors/HttpError.ts
|
|
520
535
|
var HttpError = class _HttpError extends Error {
|
|
536
|
+
/** HTTP status code */
|
|
537
|
+
code;
|
|
538
|
+
/** Lowercase error type identifier */
|
|
539
|
+
type;
|
|
540
|
+
/** Human-readable error title */
|
|
541
|
+
title;
|
|
542
|
+
/** Default error description from definition */
|
|
543
|
+
details;
|
|
544
|
+
/** Additional error metadata */
|
|
545
|
+
metadata;
|
|
546
|
+
/** Original error cause (also available as native `Error.cause`) */
|
|
547
|
+
cause;
|
|
548
|
+
/** Retry-after time in seconds (if applicable) */
|
|
549
|
+
retryAfter;
|
|
550
|
+
/** Whether `message` is safe to send to clients (4xx: true, 5xx: false by default) */
|
|
551
|
+
expose;
|
|
552
|
+
/** Stable application-level error code (e.g. "USER_NOT_FOUND") */
|
|
553
|
+
errorCode;
|
|
554
|
+
/** Structured validation issues */
|
|
555
|
+
validationErrors;
|
|
556
|
+
/** Extra HTTP headers associated with this error */
|
|
557
|
+
headers;
|
|
558
|
+
/** RFC 9457 `instance` URI for this specific occurrence */
|
|
559
|
+
instance;
|
|
521
560
|
/**
|
|
522
561
|
* Creates a new HttpError instance
|
|
523
|
-
*
|
|
562
|
+
*
|
|
524
563
|
* @param code - HTTP status code (e.g., 404, 500)
|
|
525
564
|
* @param options - Optional configuration
|
|
526
565
|
*/
|
|
527
566
|
constructor(code, options = {}) {
|
|
528
567
|
const errorInfo = getErrorDefinition(code);
|
|
529
|
-
const
|
|
530
|
-
const finalMessage = options.message ?? customMessage ?? errorInfo.details;
|
|
568
|
+
const finalMessage = options.message ?? errorInfo.details;
|
|
531
569
|
super(finalMessage);
|
|
532
570
|
this.name = "HttpError";
|
|
533
571
|
this.code = errorInfo.code;
|
|
534
572
|
this.type = errorInfo.type;
|
|
535
573
|
this.title = errorInfo.title;
|
|
574
|
+
this.details = errorInfo.details;
|
|
536
575
|
this.metadata = options.metadata;
|
|
537
|
-
this.cause = options.cause;
|
|
538
576
|
this.retryAfter = options.retryAfter ?? errorInfo.retryAfter;
|
|
577
|
+
this.expose = options.expose ?? errorInfo.code < 500;
|
|
578
|
+
this.errorCode = options.errorCode;
|
|
579
|
+
this.validationErrors = options.validationErrors;
|
|
580
|
+
this.headers = options.headers;
|
|
581
|
+
this.instance = options.instance;
|
|
582
|
+
if (options.cause !== void 0) {
|
|
583
|
+
this.cause = options.cause;
|
|
584
|
+
}
|
|
539
585
|
if (typeof Error.captureStackTrace === "function") {
|
|
540
586
|
Error.captureStackTrace(this, _HttpError);
|
|
541
587
|
}
|
|
542
588
|
}
|
|
543
589
|
/**
|
|
544
|
-
*
|
|
590
|
+
* The message that is safe to send to clients.
|
|
591
|
+
* Falls back to the generic definition message when `expose` is false.
|
|
592
|
+
*/
|
|
593
|
+
get safeMessage() {
|
|
594
|
+
return this.expose ? this.message : this.details;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* HTTP headers that should accompany this error response
|
|
598
|
+
* (e.g. `Retry-After` for 429/503, plus any custom headers).
|
|
599
|
+
*/
|
|
600
|
+
getHeaders() {
|
|
601
|
+
const headers = { ...this.headers };
|
|
602
|
+
if (this.retryAfter !== void 0) {
|
|
603
|
+
headers["Retry-After"] = String(this.retryAfter);
|
|
604
|
+
}
|
|
605
|
+
return headers;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Convert error to plain object for JSON serialization.
|
|
609
|
+
* Note: contains the full (non-sanitized) message - intended for logging.
|
|
610
|
+
* Use `kit.error()` / `toProblemDetails()` for client output.
|
|
545
611
|
*/
|
|
546
612
|
toJSON() {
|
|
547
613
|
return {
|
|
@@ -550,9 +616,56 @@ var HttpError = class _HttpError extends Error {
|
|
|
550
616
|
type: this.type,
|
|
551
617
|
title: this.title,
|
|
552
618
|
message: this.message,
|
|
619
|
+
details: this.details,
|
|
620
|
+
errorCode: this.errorCode,
|
|
553
621
|
metadata: this.metadata,
|
|
554
|
-
retryAfter: this.retryAfter
|
|
622
|
+
retryAfter: this.retryAfter,
|
|
623
|
+
expose: this.expose,
|
|
624
|
+
validationErrors: this.validationErrors
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Build an RFC 9457 Problem Details object for this error.
|
|
629
|
+
* Serve it with `Content-Type: application/problem+json`.
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```ts
|
|
633
|
+
* const problem = HttpError.notFound('User not found').toProblemDetails({
|
|
634
|
+
* typeBase: 'https://errors.example.com',
|
|
635
|
+
* instance: '/users/42',
|
|
636
|
+
* });
|
|
637
|
+
* // { type: 'https://errors.example.com/not_found', title: 'Not Found',
|
|
638
|
+
* // status: 404, detail: 'User not found', instance: '/users/42' }
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
toProblemDetails(options = {}) {
|
|
642
|
+
const exposed = options.expose ?? this.expose;
|
|
643
|
+
const problem = {
|
|
644
|
+
type: options.typeBase ? `${options.typeBase.replace(/\/$/, "")}/${this.type}` : "about:blank",
|
|
645
|
+
title: this.title,
|
|
646
|
+
status: this.code,
|
|
647
|
+
detail: exposed ? this.message : this.details
|
|
555
648
|
};
|
|
649
|
+
if (options.instance ?? this.instance) {
|
|
650
|
+
problem.instance = options.instance ?? this.instance;
|
|
651
|
+
}
|
|
652
|
+
if (this.errorCode) {
|
|
653
|
+
problem.code = this.errorCode;
|
|
654
|
+
}
|
|
655
|
+
if (this.validationErrors?.length) {
|
|
656
|
+
problem.errors = this.validationErrors;
|
|
657
|
+
}
|
|
658
|
+
if (options.requestId) {
|
|
659
|
+
problem.request_id = options.requestId;
|
|
660
|
+
}
|
|
661
|
+
if (options.extensions) {
|
|
662
|
+
for (const [key, value] of Object.entries(options.extensions)) {
|
|
663
|
+
if (!(key in problem)) {
|
|
664
|
+
problem[key] = value;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return problem;
|
|
556
669
|
}
|
|
557
670
|
// ========================================================================
|
|
558
671
|
// Factory Methods - 4xx Client Errors
|
|
@@ -641,6 +754,20 @@ var HttpError = class _HttpError extends Error {
|
|
|
641
754
|
static unprocessableEntity(message, metadata) {
|
|
642
755
|
return new _HttpError(422, { message, metadata });
|
|
643
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* 422 Unprocessable Entity with structured validation issues.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* throw HttpError.validation([
|
|
763
|
+
* { field: 'email', message: 'Invalid email format', code: 'invalid_format' },
|
|
764
|
+
* { field: 'age', message: 'Must be >= 18', code: 'too_small' },
|
|
765
|
+
* ]);
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
static validation(errors, message = "Validation failed", code = 422) {
|
|
769
|
+
return new _HttpError(code, { message, validationErrors: errors, errorCode: "VALIDATION_FAILED" });
|
|
770
|
+
}
|
|
644
771
|
/** 423 Locked */
|
|
645
772
|
static locked(message, metadata) {
|
|
646
773
|
return new _HttpError(423, { message, metadata });
|
|
@@ -728,20 +855,41 @@ var HttpError = class _HttpError extends Error {
|
|
|
728
855
|
// Utility Methods
|
|
729
856
|
// ========================================================================
|
|
730
857
|
/**
|
|
731
|
-
* Create an HttpError from an unknown error
|
|
858
|
+
* Create an HttpError from an unknown error.
|
|
859
|
+
*
|
|
860
|
+
* Security: wrapped errors are marked `expose: false` when the resulting
|
|
861
|
+
* status is 5xx, so unexpected internal messages (DB errors, stack info,
|
|
862
|
+
* connection strings...) are never sent to clients. The original message
|
|
863
|
+
* and cause are preserved on the instance for logging.
|
|
864
|
+
*
|
|
865
|
+
* System/socket errors (ECONNREFUSED, ETIMEDOUT, undici fetch codes...)
|
|
866
|
+
* are automatically mapped to the semantically correct 502/503/504 -
|
|
867
|
+
* including codes buried in the `cause` chain (e.g. Node's
|
|
868
|
+
* `fetch failed` TypeError) - with the syscall code as `errorCode`.
|
|
732
869
|
*/
|
|
733
870
|
static fromError(error, fallbackCode = 500) {
|
|
734
871
|
if (error instanceof _HttpError) {
|
|
735
872
|
return error;
|
|
736
873
|
}
|
|
874
|
+
if (fallbackCode < 400 || fallbackCode > 599) {
|
|
875
|
+
throw new RangeError(
|
|
876
|
+
`fallbackCode must be between 400 and 599, got ${fallbackCode}`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
const systemError = mapSystemError(error);
|
|
880
|
+
if (systemError !== void 0) {
|
|
881
|
+
return systemError;
|
|
882
|
+
}
|
|
737
883
|
if (error instanceof Error) {
|
|
738
884
|
return new _HttpError(fallbackCode, {
|
|
739
885
|
message: error.message,
|
|
740
|
-
cause: error
|
|
886
|
+
cause: error,
|
|
887
|
+
expose: fallbackCode < 500
|
|
741
888
|
});
|
|
742
889
|
}
|
|
743
890
|
return new _HttpError(fallbackCode, {
|
|
744
|
-
message: String(error)
|
|
891
|
+
message: String(error),
|
|
892
|
+
expose: fallbackCode < 500
|
|
745
893
|
});
|
|
746
894
|
}
|
|
747
895
|
/**
|
|
@@ -762,32 +910,131 @@ var HttpError = class _HttpError extends Error {
|
|
|
762
910
|
isServerError() {
|
|
763
911
|
return this.code >= 500 && this.code < 600;
|
|
764
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Flattened chain of cause messages (most recent first).
|
|
915
|
+
* Useful for structured logging; never serialized to clients in production.
|
|
916
|
+
*/
|
|
917
|
+
getCauseChain() {
|
|
918
|
+
const chain = [];
|
|
919
|
+
let current = this.cause;
|
|
920
|
+
let depth = 0;
|
|
921
|
+
while (current instanceof Error && depth < 10) {
|
|
922
|
+
chain.push(`${current.name}: ${current.message}`);
|
|
923
|
+
current = current.cause;
|
|
924
|
+
depth++;
|
|
925
|
+
}
|
|
926
|
+
return chain;
|
|
927
|
+
}
|
|
765
928
|
};
|
|
766
929
|
|
|
767
|
-
// src/
|
|
768
|
-
|
|
930
|
+
// src/errors/catalog.ts
|
|
931
|
+
function createErrorCatalog(catalog) {
|
|
932
|
+
const factories = {};
|
|
933
|
+
for (const key of Object.keys(catalog)) {
|
|
934
|
+
const entry = catalog[key];
|
|
935
|
+
factories[key] = (message, options = {}) => new HttpError(entry.status, {
|
|
936
|
+
...options,
|
|
937
|
+
message: message ?? entry.message ?? options.message,
|
|
938
|
+
errorCode: String(key),
|
|
939
|
+
expose: options.expose ?? entry.expose,
|
|
940
|
+
metadata: options.metadata ?? entry.metadata
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return factories;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/kit.ts
|
|
947
|
+
function snakeToCamel(key) {
|
|
948
|
+
return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
949
|
+
}
|
|
950
|
+
function camelizeResponse(response) {
|
|
951
|
+
const out = {};
|
|
952
|
+
for (const [key, value] of Object.entries(response)) {
|
|
953
|
+
out[snakeToCamel(key)] = value;
|
|
954
|
+
}
|
|
955
|
+
const metadata = out.metadata;
|
|
956
|
+
if (metadata && typeof metadata === "object" && metadata.pagination && typeof metadata.pagination === "object") {
|
|
957
|
+
const pagination = {};
|
|
958
|
+
for (const [key, value] of Object.entries(metadata.pagination)) {
|
|
959
|
+
pagination[snakeToCamel(key)] = value;
|
|
960
|
+
}
|
|
961
|
+
out.metadata = { ...metadata, pagination };
|
|
962
|
+
}
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
function isSuccessResponse(response) {
|
|
966
|
+
return response.success === true;
|
|
967
|
+
}
|
|
968
|
+
function isErrorResponse(response) {
|
|
969
|
+
return response.success === false;
|
|
970
|
+
}
|
|
971
|
+
var ResponseKit = class {
|
|
972
|
+
config;
|
|
973
|
+
constructor(config = {}) {
|
|
974
|
+
this.config = {
|
|
975
|
+
includeTimestamp: true,
|
|
976
|
+
format: "standard",
|
|
977
|
+
casing: "snake",
|
|
978
|
+
exposeServerErrors: false,
|
|
979
|
+
...config,
|
|
980
|
+
customMessages: { ...config.customMessages }
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/** Merge partial configuration into this instance */
|
|
984
|
+
configure(config) {
|
|
985
|
+
Object.assign(this.config, {
|
|
986
|
+
...config,
|
|
987
|
+
customMessages: { ...this.config.customMessages, ...config.customMessages }
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
/** Snapshot of the current configuration */
|
|
991
|
+
getConfig() {
|
|
992
|
+
return { ...this.config };
|
|
993
|
+
}
|
|
994
|
+
/** Whether dev mode is active (stack traces, cause chains) */
|
|
995
|
+
isDevelopment() {
|
|
996
|
+
return this.config.isDevelopment ?? process.env.NODE_ENV === "development";
|
|
997
|
+
}
|
|
998
|
+
finalize(response) {
|
|
999
|
+
let out = this.config.casing === "camel" ? camelizeResponse(response) : response;
|
|
1000
|
+
const transformer = this.config.responseTransformer;
|
|
1001
|
+
if (transformer) {
|
|
1002
|
+
out = transformer(out);
|
|
1003
|
+
}
|
|
1004
|
+
return out;
|
|
1005
|
+
}
|
|
769
1006
|
/**
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
1007
|
+
* Resolve the outgoing client-facing message for an error.
|
|
1008
|
+
*
|
|
1009
|
+
* Priority: messageResolver > explicit message (if exposed)
|
|
1010
|
+
* > customMessages[code] > generic status description.
|
|
774
1011
|
*/
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1012
|
+
resolveMessage(error, exposed) {
|
|
1013
|
+
const resolved = this.config.messageResolver?.(error);
|
|
1014
|
+
if (resolved !== void 0) return resolved;
|
|
1015
|
+
const customDefault = this.config.customMessages?.[error.code];
|
|
1016
|
+
const hasExplicitMessage = error.message !== error.details;
|
|
1017
|
+
if (exposed && hasExplicitMessage) return error.message;
|
|
1018
|
+
return customDefault ?? error.details;
|
|
1019
|
+
}
|
|
1020
|
+
// ========================================================================
|
|
1021
|
+
// Success
|
|
1022
|
+
// ========================================================================
|
|
1023
|
+
/** Format a success response */
|
|
1024
|
+
success(config = {}) {
|
|
1025
|
+
const { data, message, statusCode = 200 /* OK */, metadata = {}, requestId } = config;
|
|
782
1026
|
const successInfo = getSuccessDefinition(statusCode);
|
|
783
1027
|
const response = {
|
|
784
1028
|
success: true,
|
|
785
1029
|
status_code: successInfo.code
|
|
786
1030
|
};
|
|
787
|
-
if (
|
|
1031
|
+
if (this.config.includeTimestamp ?? true) {
|
|
788
1032
|
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
789
1033
|
}
|
|
790
|
-
if (
|
|
1034
|
+
if (requestId) {
|
|
1035
|
+
response.request_id = requestId;
|
|
1036
|
+
}
|
|
1037
|
+
if (statusCode !== 204 /* NO_CONTENT */ && statusCode !== 205 /* RESET_CONTENT */ && statusCode !== 304 /* NOT_MODIFIED */) {
|
|
791
1038
|
if (data !== null && data !== void 0) {
|
|
792
1039
|
response.data = data;
|
|
793
1040
|
}
|
|
@@ -798,142 +1045,195 @@ var HttpResponse = class _HttpResponse {
|
|
|
798
1045
|
if (Object.keys(metadata).length > 0) {
|
|
799
1046
|
response.metadata = metadata;
|
|
800
1047
|
}
|
|
801
|
-
|
|
802
|
-
if (transformer) {
|
|
803
|
-
return transformer(response);
|
|
804
|
-
}
|
|
805
|
-
return response;
|
|
1048
|
+
return this.finalize(response);
|
|
806
1049
|
}
|
|
1050
|
+
// ========================================================================
|
|
1051
|
+
// Error
|
|
1052
|
+
// ========================================================================
|
|
807
1053
|
/**
|
|
808
|
-
* Format an error response
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
1054
|
+
* Format an error response.
|
|
1055
|
+
*
|
|
1056
|
+
* Security: when the error is not exposable (5xx by default), the outgoing
|
|
1057
|
+
* message falls back to the generic status description (or the configured
|
|
1058
|
+
* custom message for that code) and `metadata` is omitted. The original
|
|
1059
|
+
* message stays available on the error instance for logging.
|
|
813
1060
|
*/
|
|
814
|
-
|
|
815
|
-
const { includeStack, additionalFields } = config;
|
|
1061
|
+
error(error, config = {}) {
|
|
1062
|
+
const { includeStack, additionalFields, requestId } = config;
|
|
1063
|
+
const cfg = this.config;
|
|
1064
|
+
const exposed = config.expose ?? (error.expose || error.isServerError() && cfg.exposeServerErrors === true);
|
|
816
1065
|
const response = {
|
|
817
1066
|
success: false,
|
|
818
1067
|
status_code: error.code,
|
|
819
1068
|
error: {
|
|
820
1069
|
type: error.type,
|
|
821
1070
|
title: error.title,
|
|
822
|
-
message: error
|
|
1071
|
+
message: this.resolveMessage(error, exposed)
|
|
823
1072
|
}
|
|
824
1073
|
};
|
|
825
|
-
if (
|
|
1074
|
+
if (cfg.includeTimestamp ?? true) {
|
|
826
1075
|
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
827
1076
|
}
|
|
828
|
-
if (
|
|
829
|
-
response.
|
|
1077
|
+
if (requestId) {
|
|
1078
|
+
response.request_id = requestId;
|
|
1079
|
+
}
|
|
1080
|
+
if (error.errorCode) {
|
|
1081
|
+
response.error.code = error.errorCode;
|
|
1082
|
+
}
|
|
1083
|
+
if (error.details) {
|
|
1084
|
+
response.error.details = error.details;
|
|
1085
|
+
}
|
|
1086
|
+
if (error.validationErrors?.length) {
|
|
1087
|
+
response.error.errors = error.validationErrors;
|
|
1088
|
+
}
|
|
1089
|
+
if (includeStack ?? this.isDevelopment()) {
|
|
1090
|
+
response.error.stack = error.stack;
|
|
1091
|
+
const causes = error.getCauseChain();
|
|
1092
|
+
if (causes.length > 0) {
|
|
1093
|
+
response.error.causes = causes;
|
|
1094
|
+
}
|
|
830
1095
|
}
|
|
831
1096
|
if (error.retryAfter) {
|
|
832
1097
|
response.retry_after = error.retryAfter;
|
|
833
1098
|
}
|
|
834
|
-
if (error.metadata) {
|
|
835
|
-
|
|
1099
|
+
if (error.metadata && exposed) {
|
|
1100
|
+
const sanitized = cfg.metadataSanitizer ? cfg.metadataSanitizer(error.metadata) : error.metadata;
|
|
1101
|
+
if (Object.keys(sanitized).length > 0) {
|
|
1102
|
+
response.metadata = sanitized;
|
|
1103
|
+
}
|
|
836
1104
|
}
|
|
837
1105
|
if (additionalFields) {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const transformer = getResponseTransformer();
|
|
841
|
-
if (transformer) {
|
|
842
|
-
return transformer(response);
|
|
1106
|
+
const { success, status_code, error: _error, timestamp, metadata, retry_after, request_id, ...safeFields } = additionalFields;
|
|
1107
|
+
Object.assign(response, safeFields);
|
|
843
1108
|
}
|
|
844
|
-
return response;
|
|
1109
|
+
return this.finalize(response);
|
|
1110
|
+
}
|
|
1111
|
+
/** Format a response from any error (converts to HttpError first) */
|
|
1112
|
+
fromError(error, config = {}) {
|
|
1113
|
+
const httpError = HttpError.fromError(error, config.fallbackCode);
|
|
1114
|
+
return this.error(httpError, config);
|
|
845
1115
|
}
|
|
846
1116
|
/**
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* @param config - Optional error response configuration
|
|
851
|
-
* @returns Formatted error response object
|
|
1117
|
+
* Build an RFC 9457 Problem Details body from any error.
|
|
1118
|
+
* Serve it with `Content-Type: application/problem+json`
|
|
1119
|
+
* (see `PROBLEM_CONTENT_TYPE`).
|
|
852
1120
|
*/
|
|
853
|
-
|
|
1121
|
+
problem(error, options = {}) {
|
|
854
1122
|
const httpError = HttpError.fromError(error);
|
|
855
|
-
|
|
1123
|
+
const exposed = options.expose ?? httpError.expose;
|
|
1124
|
+
const problem = httpError.toProblemDetails({
|
|
1125
|
+
typeBase: options.typeBase ?? this.config.problemTypeBase,
|
|
1126
|
+
...options
|
|
1127
|
+
});
|
|
1128
|
+
problem.detail = this.resolveMessage(httpError, exposed);
|
|
1129
|
+
return problem;
|
|
856
1130
|
}
|
|
857
1131
|
// ========================================================================
|
|
858
1132
|
// Convenience Methods
|
|
859
1133
|
// ========================================================================
|
|
860
1134
|
/** 200 OK */
|
|
861
|
-
|
|
862
|
-
return
|
|
1135
|
+
ok(data, message) {
|
|
1136
|
+
return this.success({ data, message, statusCode: 200 /* OK */ });
|
|
863
1137
|
}
|
|
864
1138
|
/** 201 Created */
|
|
865
|
-
|
|
866
|
-
return
|
|
1139
|
+
created(data, message) {
|
|
1140
|
+
return this.success({ data, message, statusCode: 201 /* CREATED */ });
|
|
867
1141
|
}
|
|
868
1142
|
/** 202 Accepted */
|
|
869
|
-
|
|
870
|
-
return
|
|
1143
|
+
accepted(data, message) {
|
|
1144
|
+
return this.success({ data, message, statusCode: 202 /* ACCEPTED */ });
|
|
871
1145
|
}
|
|
872
1146
|
/** 204 No Content */
|
|
873
|
-
|
|
874
|
-
return
|
|
1147
|
+
noContent() {
|
|
1148
|
+
return this.success({ statusCode: 204 /* NO_CONTENT */ });
|
|
875
1149
|
}
|
|
876
1150
|
/** 206 Partial Content */
|
|
877
|
-
|
|
878
|
-
return
|
|
1151
|
+
partialContent(data, message) {
|
|
1152
|
+
return this.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
|
|
879
1153
|
}
|
|
880
1154
|
/** 304 Not Modified */
|
|
881
|
-
|
|
882
|
-
return
|
|
1155
|
+
notModified() {
|
|
1156
|
+
return this.success({ statusCode: 304 /* NOT_MODIFIED */ });
|
|
883
1157
|
}
|
|
884
1158
|
// ========================================================================
|
|
885
|
-
//
|
|
1159
|
+
// Pagination
|
|
886
1160
|
// ========================================================================
|
|
887
|
-
/**
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Check if a response is an error response
|
|
895
|
-
*/
|
|
896
|
-
static isError(response) {
|
|
897
|
-
return response.success === false;
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Create a paginated success response
|
|
901
|
-
*/
|
|
902
|
-
static paginated(data, pagination, message) {
|
|
903
|
-
return _HttpResponse.success({
|
|
1161
|
+
/** Create an offset-based paginated success response */
|
|
1162
|
+
paginated(data, pagination, message) {
|
|
1163
|
+
const effectiveLimit = pagination.limit > 0 ? pagination.limit : 1;
|
|
1164
|
+
const totalPages = pagination.totalPages ?? Math.ceil(pagination.total / effectiveLimit);
|
|
1165
|
+
return this.success({
|
|
904
1166
|
data,
|
|
905
1167
|
message,
|
|
906
1168
|
metadata: {
|
|
907
1169
|
pagination: {
|
|
908
1170
|
page: pagination.page,
|
|
909
|
-
limit:
|
|
1171
|
+
limit: effectiveLimit,
|
|
910
1172
|
total: pagination.total,
|
|
911
|
-
total_pages:
|
|
912
|
-
has_next: pagination.page <
|
|
1173
|
+
total_pages: totalPages,
|
|
1174
|
+
has_next: pagination.page < totalPages,
|
|
913
1175
|
has_prev: pagination.page > 1
|
|
914
1176
|
}
|
|
915
1177
|
}
|
|
916
1178
|
});
|
|
917
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Create a cursor-based paginated success response.
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* kit.paginatedCursor(items, { nextCursor: 'eyJpZCI6NDJ9', limit: 20 });
|
|
1186
|
+
* // metadata.pagination: { next_cursor, limit, has_next: true, has_prev: false }
|
|
1187
|
+
* ```
|
|
1188
|
+
*/
|
|
1189
|
+
paginatedCursor(data, cursor, message) {
|
|
1190
|
+
const pagination = {
|
|
1191
|
+
has_next: cursor.nextCursor !== void 0 && cursor.nextCursor !== null,
|
|
1192
|
+
has_prev: cursor.prevCursor !== void 0 && cursor.prevCursor !== null
|
|
1193
|
+
};
|
|
1194
|
+
if (cursor.nextCursor !== void 0) pagination.next_cursor = cursor.nextCursor;
|
|
1195
|
+
if (cursor.prevCursor !== void 0) pagination.prev_cursor = cursor.prevCursor;
|
|
1196
|
+
if (cursor.limit !== void 0) pagination.limit = cursor.limit;
|
|
1197
|
+
if (cursor.total !== void 0) pagination.total = cursor.total;
|
|
1198
|
+
return this.success({ data, message, metadata: { pagination } });
|
|
1199
|
+
}
|
|
918
1200
|
};
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1201
|
+
function createResponseKit(config = {}) {
|
|
1202
|
+
return new ResponseKit(config);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/responses/problem.ts
|
|
1206
|
+
var PROBLEM_CONTENT_TYPE = "application/problem+json";
|
|
1207
|
+
function toProblem(error, options = {}) {
|
|
1208
|
+
const httpError = HttpError.fromError(error);
|
|
1209
|
+
return httpError.toProblemDetails(options);
|
|
1210
|
+
}
|
|
1211
|
+
function isProblemDetails(value) {
|
|
1212
|
+
return typeof value === "object" && value !== null && typeof value.title === "string" && typeof value.status === "number" && typeof value.type === "string";
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
exports.HttpClientErrorCode = HttpClientErrorCode;
|
|
1216
|
+
exports.HttpError = HttpError;
|
|
1217
|
+
exports.HttpErrorCode = HttpErrorCode;
|
|
1218
|
+
exports.HttpErrorDefinitions = HttpErrorDefinitions;
|
|
1219
|
+
exports.HttpInfoCode = HttpInfoCode;
|
|
1220
|
+
exports.HttpInfoDefinitions = HttpInfoDefinitions;
|
|
1221
|
+
exports.HttpRedirectCode = HttpRedirectCode;
|
|
1222
|
+
exports.HttpRedirectDefinitions = HttpRedirectDefinitions;
|
|
1223
|
+
exports.HttpServerErrorCode = HttpServerErrorCode;
|
|
1224
|
+
exports.HttpSuccessCode = HttpSuccessCode;
|
|
1225
|
+
exports.HttpSuccessDefinitions = HttpSuccessDefinitions;
|
|
1226
|
+
exports.PROBLEM_CONTENT_TYPE = PROBLEM_CONTENT_TYPE;
|
|
1227
|
+
exports.ResponseKit = ResponseKit;
|
|
1228
|
+
exports.SystemErrorStatusMap = SystemErrorStatusMap;
|
|
1229
|
+
exports.createErrorCatalog = createErrorCatalog;
|
|
1230
|
+
exports.createResponseKit = createResponseKit;
|
|
1231
|
+
exports.getErrorDefinition = getErrorDefinition;
|
|
1232
|
+
exports.getSuccessDefinition = getSuccessDefinition;
|
|
1233
|
+
exports.isErrorResponse = isErrorResponse;
|
|
1234
|
+
exports.isProblemDetails = isProblemDetails;
|
|
1235
|
+
exports.isSuccessResponse = isSuccessResponse;
|
|
1236
|
+
exports.mapSystemError = mapSystemError;
|
|
1237
|
+
exports.toProblem = toProblem;
|
|
1238
|
+
//# sourceMappingURL=index.js.map
|
|
1239
|
+
//# sourceMappingURL=index.js.map
|