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.
Files changed (47) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +357 -281
  3. package/dist/chunk-373JVXMN.mjs +1199 -0
  4. package/dist/chunk-373JVXMN.mjs.map +1 -0
  5. package/dist/chunk-FFRB2EUE.mjs +36 -0
  6. package/dist/chunk-FFRB2EUE.mjs.map +1 -0
  7. package/dist/express.d.mts +50 -0
  8. package/dist/express.d.ts +50 -0
  9. package/dist/express.js +1155 -0
  10. package/dist/express.js.map +1 -0
  11. package/dist/express.mjs +28 -0
  12. package/dist/express.mjs.map +1 -0
  13. package/dist/fastify.d.mts +45 -0
  14. package/dist/fastify.d.ts +45 -0
  15. package/dist/fastify.js +1170 -0
  16. package/dist/fastify.js.map +1 -0
  17. package/dist/fastify.mjs +43 -0
  18. package/dist/fastify.mjs.map +1 -0
  19. package/dist/hono.d.mts +33 -0
  20. package/dist/hono.d.ts +33 -0
  21. package/dist/hono.js +1139 -0
  22. package/dist/hono.js.map +1 -0
  23. package/dist/hono.mjs +18 -0
  24. package/dist/hono.mjs.map +1 -0
  25. package/dist/index.d.mts +75 -527
  26. package/dist/index.d.ts +75 -527
  27. package/dist/index.js +440 -188
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.mjs +18 -939
  30. package/dist/index.mjs.map +1 -0
  31. package/dist/kit-nHfihlKE.d.mts +753 -0
  32. package/dist/kit-nHfihlKE.d.ts +753 -0
  33. package/dist/koa.d.mts +38 -0
  34. package/dist/koa.d.ts +38 -0
  35. package/dist/koa.js +1150 -0
  36. package/dist/koa.js.map +1 -0
  37. package/dist/koa.mjs +24 -0
  38. package/dist/koa.mjs.map +1 -0
  39. package/dist/schemas.d.mts +452 -0
  40. package/dist/schemas.d.ts +452 -0
  41. package/dist/schemas.js +124 -0
  42. package/dist/schemas.js.map +1 -0
  43. package/dist/schemas.mjs +119 -0
  44. package/dist/schemas.mjs.map +1 -0
  45. package/dist/shared-Czwmz6Xa.d.ts +22 -0
  46. package/dist/shared-DYq1Fic4.d.mts +22 -0
  47. package/package.json +104 -53
package/dist/index.js CHANGED
@@ -1,45 +1,4 @@
1
- "use strict";
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/config/index.ts
515
- var defaultConfig = {
516
- isDevelopment: void 0,
517
- includeTimestamp: true,
518
- customMessages: {},
519
- responseTransformer: void 0
520
- };
521
- var currentConfig = { ...defaultConfig };
522
- function configure(config) {
523
- currentConfig = {
524
- ...currentConfig,
525
- ...config,
526
- customMessages: { ...currentConfig.customMessages, ...config.customMessages }
527
- };
528
- }
529
- function getConfig() {
530
- return { ...currentConfig };
531
- }
532
- function resetConfig() {
533
- currentConfig = { ...defaultConfig, customMessages: {} };
534
- }
535
- function isDevelopment() {
536
- return currentConfig.isDevelopment ?? process.env.NODE_ENV === "development";
537
- }
538
- function shouldIncludeTimestamp() {
539
- return currentConfig.includeTimestamp ?? true;
540
- }
541
- function getCustomMessage(code) {
542
- return currentConfig.customMessages?.[code];
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 getResponseTransformer() {
545
- return currentConfig.responseTransformer;
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 customMessage = getCustomMessage(code);
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
- * Convert error to plain object for JSON serialization
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 a status code with default definition values.
763
- * Semantic alias for `new HttpError(code)`.
764
- */
765
- static fromStatus(code, options) {
766
- return new _HttpError(code, options);
767
- }
768
- /**
769
- * 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`.
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/responses/HttpResponse.ts
811
- var HttpResponse = class _HttpResponse {
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
- * Format a success response
814
- *
815
- * @param config - Success response configuration
816
- * @returns Formatted success response object
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
- static success(config = {}) {
819
- const {
820
- data,
821
- message,
822
- statusCode = 200 /* OK */,
823
- metadata = {}
824
- } = config;
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 (shouldIncludeTimestamp()) {
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
- const transformer = getResponseTransformer();
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
- * @param error - HttpError instance
854
- * @param config - Optional error response configuration
855
- * @returns Formatted error response object
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
- static error(error, config = {}) {
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.message
1071
+ message: this.resolveMessage(error, exposed)
866
1072
  }
867
1073
  };
868
- if (shouldIncludeTimestamp()) {
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 (includeStack ?? isDevelopment()) {
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
- response.metadata = error.metadata;
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
- const transformer = getResponseTransformer();
888
- if (transformer) {
889
- return transformer(response);
890
- }
891
- 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);
892
1115
  }
893
1116
  /**
894
- * Format a response from any error (converts to HttpError first)
895
- *
896
- * @param error - Any error type
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
- static fromError(error, config = {}) {
901
- const httpError = HttpError.fromError(error, config.fallbackCode);
902
- return _HttpResponse.error(httpError, config);
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
- static ok(data, message) {
909
- return _HttpResponse.success({ data, message, statusCode: 200 /* OK */ });
1135
+ ok(data, message) {
1136
+ return this.success({ data, message, statusCode: 200 /* OK */ });
910
1137
  }
911
1138
  /** 201 Created */
912
- static created(data, message) {
913
- return _HttpResponse.success({ data, message, statusCode: 201 /* CREATED */ });
1139
+ created(data, message) {
1140
+ return this.success({ data, message, statusCode: 201 /* CREATED */ });
914
1141
  }
915
1142
  /** 202 Accepted */
916
- static accepted(data, message) {
917
- return _HttpResponse.success({ data, message, statusCode: 202 /* ACCEPTED */ });
1143
+ accepted(data, message) {
1144
+ return this.success({ data, message, statusCode: 202 /* ACCEPTED */ });
918
1145
  }
919
1146
  /** 204 No Content */
920
- static noContent() {
921
- return _HttpResponse.success({ statusCode: 204 /* NO_CONTENT */ });
1147
+ noContent() {
1148
+ return this.success({ statusCode: 204 /* NO_CONTENT */ });
922
1149
  }
923
1150
  /** 206 Partial Content */
924
- static partialContent(data, message) {
925
- return _HttpResponse.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
1151
+ partialContent(data, message) {
1152
+ return this.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
926
1153
  }
927
- /** 304 Not Modified — Note: 304 is a 3xx redirect code, included here as a convenience method */
928
- static notModified() {
929
- return _HttpResponse.success({ statusCode: 304 /* NOT_MODIFIED */ });
1154
+ /** 304 Not Modified */
1155
+ notModified() {
1156
+ return this.success({ statusCode: 304 /* NOT_MODIFIED */ });
930
1157
  }
931
1158
  // ========================================================================
932
- // Utility Methods
1159
+ // Pagination
933
1160
  // ========================================================================
934
- /**
935
- * Check if a response is a success response
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
- return _HttpResponse.success({
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: pagination.totalPages ?? Math.ceil(pagination.total / effectiveLimit),
960
- has_next: pagination.page < (pagination.totalPages ?? Math.ceil(pagination.total / effectiveLimit)),
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
- // Annotate the CommonJS export names for ESM import in node:
968
- 0 && (module.exports = {
969
- HttpClientErrorCode,
970
- HttpError,
971
- HttpErrorCode,
972
- HttpErrorDefinitions,
973
- HttpInfoCode,
974
- HttpInfoDefinitions,
975
- HttpRedirectCode,
976
- HttpRedirectDefinitions,
977
- HttpResponse,
978
- HttpServerErrorCode,
979
- HttpSuccessCode,
980
- HttpSuccessDefinitions,
981
- configure,
982
- getConfig,
983
- getErrorDefinition,
984
- getSuccessDefinition,
985
- isDevelopment,
986
- resetConfig
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