http-response-kit 1.1.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 -281
- 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 -527
- package/dist/index.d.ts +75 -527
- package/dist/index.js +440 -188
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +18 -939
- 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 -53
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) => {
|
|
@@ -511,52 +470,102 @@ function getSuccessDefinition(code) {
|
|
|
511
470
|
};
|
|
512
471
|
}
|
|
513
472
|
|
|
514
|
-
// src/
|
|
515
|
-
var
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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;
|
|
543
519
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
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
|
+
});
|
|
546
532
|
}
|
|
547
533
|
|
|
548
534
|
// src/errors/HttpError.ts
|
|
549
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;
|
|
550
560
|
/**
|
|
551
561
|
* Creates a new HttpError instance
|
|
552
|
-
*
|
|
562
|
+
*
|
|
553
563
|
* @param code - HTTP status code (e.g., 404, 500)
|
|
554
564
|
* @param options - Optional configuration
|
|
555
565
|
*/
|
|
556
566
|
constructor(code, options = {}) {
|
|
557
567
|
const errorInfo = getErrorDefinition(code);
|
|
558
|
-
const
|
|
559
|
-
const finalMessage = options.message ?? customMessage ?? errorInfo.details;
|
|
568
|
+
const finalMessage = options.message ?? errorInfo.details;
|
|
560
569
|
super(finalMessage);
|
|
561
570
|
this.name = "HttpError";
|
|
562
571
|
this.code = errorInfo.code;
|
|
@@ -564,14 +573,41 @@ var HttpError = class _HttpError extends Error {
|
|
|
564
573
|
this.title = errorInfo.title;
|
|
565
574
|
this.details = errorInfo.details;
|
|
566
575
|
this.metadata = options.metadata;
|
|
567
|
-
this.cause = options.cause;
|
|
568
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
|
+
}
|
|
569
585
|
if (typeof Error.captureStackTrace === "function") {
|
|
570
586
|
Error.captureStackTrace(this, _HttpError);
|
|
571
587
|
}
|
|
572
588
|
}
|
|
573
589
|
/**
|
|
574
|
-
*
|
|
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.
|
|
575
611
|
*/
|
|
576
612
|
toJSON() {
|
|
577
613
|
return {
|
|
@@ -581,10 +617,56 @@ var HttpError = class _HttpError extends Error {
|
|
|
581
617
|
title: this.title,
|
|
582
618
|
message: this.message,
|
|
583
619
|
details: this.details,
|
|
620
|
+
errorCode: this.errorCode,
|
|
584
621
|
metadata: this.metadata,
|
|
585
|
-
retryAfter: this.retryAfter
|
|
622
|
+
retryAfter: this.retryAfter,
|
|
623
|
+
expose: this.expose,
|
|
624
|
+
validationErrors: this.validationErrors
|
|
586
625
|
};
|
|
587
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
|
|
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;
|
|
669
|
+
}
|
|
588
670
|
// ========================================================================
|
|
589
671
|
// Factory Methods - 4xx Client Errors
|
|
590
672
|
// ========================================================================
|
|
@@ -672,6 +754,20 @@ var HttpError = class _HttpError extends Error {
|
|
|
672
754
|
static unprocessableEntity(message, metadata) {
|
|
673
755
|
return new _HttpError(422, { message, metadata });
|
|
674
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
|
+
}
|
|
675
771
|
/** 423 Locked */
|
|
676
772
|
static locked(message, metadata) {
|
|
677
773
|
return new _HttpError(423, { message, metadata });
|
|
@@ -759,14 +855,17 @@ var HttpError = class _HttpError extends Error {
|
|
|
759
855
|
// Utility Methods
|
|
760
856
|
// ========================================================================
|
|
761
857
|
/**
|
|
762
|
-
* Create an HttpError from
|
|
763
|
-
*
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
*
|
|
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`.
|
|
770
869
|
*/
|
|
771
870
|
static fromError(error, fallbackCode = 500) {
|
|
772
871
|
if (error instanceof _HttpError) {
|
|
@@ -777,14 +876,20 @@ var HttpError = class _HttpError extends Error {
|
|
|
777
876
|
`fallbackCode must be between 400 and 599, got ${fallbackCode}`
|
|
778
877
|
);
|
|
779
878
|
}
|
|
879
|
+
const systemError = mapSystemError(error);
|
|
880
|
+
if (systemError !== void 0) {
|
|
881
|
+
return systemError;
|
|
882
|
+
}
|
|
780
883
|
if (error instanceof Error) {
|
|
781
884
|
return new _HttpError(fallbackCode, {
|
|
782
885
|
message: error.message,
|
|
783
|
-
cause: error
|
|
886
|
+
cause: error,
|
|
887
|
+
expose: fallbackCode < 500
|
|
784
888
|
});
|
|
785
889
|
}
|
|
786
890
|
return new _HttpError(fallbackCode, {
|
|
787
|
-
message: String(error)
|
|
891
|
+
message: String(error),
|
|
892
|
+
expose: fallbackCode < 500
|
|
788
893
|
});
|
|
789
894
|
}
|
|
790
895
|
/**
|
|
@@ -805,31 +910,130 @@ var HttpError = class _HttpError extends Error {
|
|
|
805
910
|
isServerError() {
|
|
806
911
|
return this.code >= 500 && this.code < 600;
|
|
807
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
|
+
}
|
|
808
928
|
};
|
|
809
929
|
|
|
810
|
-
// src/
|
|
811
|
-
|
|
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
|
+
}
|
|
812
1006
|
/**
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
1007
|
+
* Resolve the outgoing client-facing message for an error.
|
|
1008
|
+
*
|
|
1009
|
+
* Priority: messageResolver > explicit message (if exposed)
|
|
1010
|
+
* > customMessages[code] > generic status description.
|
|
817
1011
|
*/
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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;
|
|
825
1026
|
const successInfo = getSuccessDefinition(statusCode);
|
|
826
1027
|
const response = {
|
|
827
1028
|
success: true,
|
|
828
1029
|
status_code: successInfo.code
|
|
829
1030
|
};
|
|
830
|
-
if (
|
|
1031
|
+
if (this.config.includeTimestamp ?? true) {
|
|
831
1032
|
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
832
1033
|
}
|
|
1034
|
+
if (requestId) {
|
|
1035
|
+
response.request_id = requestId;
|
|
1036
|
+
}
|
|
833
1037
|
if (statusCode !== 204 /* NO_CONTENT */ && statusCode !== 205 /* RESET_CONTENT */ && statusCode !== 304 /* NOT_MODIFIED */) {
|
|
834
1038
|
if (data !== null && data !== void 0) {
|
|
835
1039
|
response.data = data;
|
|
@@ -841,114 +1045,124 @@ var HttpResponse = class _HttpResponse {
|
|
|
841
1045
|
if (Object.keys(metadata).length > 0) {
|
|
842
1046
|
response.metadata = metadata;
|
|
843
1047
|
}
|
|
844
|
-
|
|
845
|
-
if (transformer) {
|
|
846
|
-
return transformer(response);
|
|
847
|
-
}
|
|
848
|
-
return response;
|
|
1048
|
+
return this.finalize(response);
|
|
849
1049
|
}
|
|
1050
|
+
// ========================================================================
|
|
1051
|
+
// Error
|
|
1052
|
+
// ========================================================================
|
|
850
1053
|
/**
|
|
851
|
-
* Format an error response
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
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.
|
|
856
1060
|
*/
|
|
857
|
-
|
|
858
|
-
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);
|
|
859
1065
|
const response = {
|
|
860
1066
|
success: false,
|
|
861
1067
|
status_code: error.code,
|
|
862
1068
|
error: {
|
|
863
1069
|
type: error.type,
|
|
864
1070
|
title: error.title,
|
|
865
|
-
message: error
|
|
1071
|
+
message: this.resolveMessage(error, exposed)
|
|
866
1072
|
}
|
|
867
1073
|
};
|
|
868
|
-
if (
|
|
1074
|
+
if (cfg.includeTimestamp ?? true) {
|
|
869
1075
|
response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
870
1076
|
}
|
|
1077
|
+
if (requestId) {
|
|
1078
|
+
response.request_id = requestId;
|
|
1079
|
+
}
|
|
1080
|
+
if (error.errorCode) {
|
|
1081
|
+
response.error.code = error.errorCode;
|
|
1082
|
+
}
|
|
871
1083
|
if (error.details) {
|
|
872
1084
|
response.error.details = error.details;
|
|
873
1085
|
}
|
|
874
|
-
if (
|
|
1086
|
+
if (error.validationErrors?.length) {
|
|
1087
|
+
response.error.errors = error.validationErrors;
|
|
1088
|
+
}
|
|
1089
|
+
if (includeStack ?? this.isDevelopment()) {
|
|
875
1090
|
response.error.stack = error.stack;
|
|
1091
|
+
const causes = error.getCauseChain();
|
|
1092
|
+
if (causes.length > 0) {
|
|
1093
|
+
response.error.causes = causes;
|
|
1094
|
+
}
|
|
876
1095
|
}
|
|
877
1096
|
if (error.retryAfter) {
|
|
878
1097
|
response.retry_after = error.retryAfter;
|
|
879
1098
|
}
|
|
880
|
-
if (error.metadata) {
|
|
881
|
-
|
|
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
|
+
}
|
|
882
1104
|
}
|
|
883
1105
|
if (additionalFields) {
|
|
884
|
-
const { success, status_code, error: _error, timestamp, metadata, retry_after, ...safeFields } = additionalFields;
|
|
1106
|
+
const { success, status_code, error: _error, timestamp, metadata, retry_after, request_id, ...safeFields } = additionalFields;
|
|
885
1107
|
Object.assign(response, safeFields);
|
|
886
1108
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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);
|
|
892
1115
|
}
|
|
893
1116
|
/**
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
* @param config - Optional error response configuration
|
|
898
|
-
* @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`).
|
|
899
1120
|
*/
|
|
900
|
-
|
|
901
|
-
const httpError = HttpError.fromError(error
|
|
902
|
-
|
|
1121
|
+
problem(error, options = {}) {
|
|
1122
|
+
const httpError = HttpError.fromError(error);
|
|
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;
|
|
903
1130
|
}
|
|
904
1131
|
// ========================================================================
|
|
905
1132
|
// Convenience Methods
|
|
906
1133
|
// ========================================================================
|
|
907
1134
|
/** 200 OK */
|
|
908
|
-
|
|
909
|
-
return
|
|
1135
|
+
ok(data, message) {
|
|
1136
|
+
return this.success({ data, message, statusCode: 200 /* OK */ });
|
|
910
1137
|
}
|
|
911
1138
|
/** 201 Created */
|
|
912
|
-
|
|
913
|
-
return
|
|
1139
|
+
created(data, message) {
|
|
1140
|
+
return this.success({ data, message, statusCode: 201 /* CREATED */ });
|
|
914
1141
|
}
|
|
915
1142
|
/** 202 Accepted */
|
|
916
|
-
|
|
917
|
-
return
|
|
1143
|
+
accepted(data, message) {
|
|
1144
|
+
return this.success({ data, message, statusCode: 202 /* ACCEPTED */ });
|
|
918
1145
|
}
|
|
919
1146
|
/** 204 No Content */
|
|
920
|
-
|
|
921
|
-
return
|
|
1147
|
+
noContent() {
|
|
1148
|
+
return this.success({ statusCode: 204 /* NO_CONTENT */ });
|
|
922
1149
|
}
|
|
923
1150
|
/** 206 Partial Content */
|
|
924
|
-
|
|
925
|
-
return
|
|
1151
|
+
partialContent(data, message) {
|
|
1152
|
+
return this.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
|
|
926
1153
|
}
|
|
927
|
-
/** 304 Not Modified
|
|
928
|
-
|
|
929
|
-
return
|
|
1154
|
+
/** 304 Not Modified */
|
|
1155
|
+
notModified() {
|
|
1156
|
+
return this.success({ statusCode: 304 /* NOT_MODIFIED */ });
|
|
930
1157
|
}
|
|
931
1158
|
// ========================================================================
|
|
932
|
-
//
|
|
1159
|
+
// Pagination
|
|
933
1160
|
// ========================================================================
|
|
934
|
-
/**
|
|
935
|
-
|
|
936
|
-
*/
|
|
937
|
-
static isSuccess(response) {
|
|
938
|
-
return response.success === true;
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Check if a response is an error response
|
|
942
|
-
*/
|
|
943
|
-
static isError(response) {
|
|
944
|
-
return response.success === false;
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Create a paginated success response
|
|
948
|
-
*/
|
|
949
|
-
static paginated(data, pagination, message) {
|
|
1161
|
+
/** Create an offset-based paginated success response */
|
|
1162
|
+
paginated(data, pagination, message) {
|
|
950
1163
|
const effectiveLimit = pagination.limit > 0 ? pagination.limit : 1;
|
|
951
|
-
|
|
1164
|
+
const totalPages = pagination.totalPages ?? Math.ceil(pagination.total / effectiveLimit);
|
|
1165
|
+
return this.success({
|
|
952
1166
|
data,
|
|
953
1167
|
message,
|
|
954
1168
|
metadata: {
|
|
@@ -956,32 +1170,70 @@ var HttpResponse = class _HttpResponse {
|
|
|
956
1170
|
page: pagination.page,
|
|
957
1171
|
limit: effectiveLimit,
|
|
958
1172
|
total: pagination.total,
|
|
959
|
-
total_pages:
|
|
960
|
-
has_next: pagination.page <
|
|
1173
|
+
total_pages: totalPages,
|
|
1174
|
+
has_next: pagination.page < totalPages,
|
|
961
1175
|
has_prev: pagination.page > 1
|
|
962
1176
|
}
|
|
963
1177
|
}
|
|
964
1178
|
});
|
|
965
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
|
+
}
|
|
966
1200
|
};
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|