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.
Files changed (47) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +357 -272
  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 -493
  26. package/dist/index.d.ts +75 -493
  27. package/dist/index.js +480 -180
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.mjs +18 -891
  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 -49
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) => {
@@ -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
- return HttpErrorDefinitions[code] || HttpErrorDefinitions[500];
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
- return HttpSuccessDefinitions[code] || HttpRedirectDefinitions[code] || HttpInfoDefinitions[code] || HttpSuccessDefinitions[200];
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/config/index.ts
490
- var defaultConfig = {
491
- isDevelopment: process.env.NODE_ENV === "development",
492
- includeTimestamp: true,
493
- customMessages: {},
494
- responseTransformer: void 0
495
- };
496
- var currentConfig = { ...defaultConfig };
497
- function configure(config) {
498
- currentConfig = { ...currentConfig, ...config };
499
- }
500
- function getConfig() {
501
- return { ...currentConfig };
502
- }
503
- function resetConfig() {
504
- currentConfig = { ...defaultConfig };
505
- }
506
- function isDevelopment() {
507
- return currentConfig.isDevelopment ?? false;
508
- }
509
- function shouldIncludeTimestamp() {
510
- return currentConfig.includeTimestamp ?? true;
511
- }
512
- function getCustomMessage(code) {
513
- 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;
514
519
  }
515
- function getResponseTransformer() {
516
- 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
+ });
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 customMessage = getCustomMessage(code);
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
- * 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.
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/responses/HttpResponse.ts
768
- 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
+ }
769
1006
  /**
770
- * Format a success response
771
- *
772
- * @param config - Success response configuration
773
- * @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.
774
1011
  */
775
- static success(config = {}) {
776
- const {
777
- data = null,
778
- message,
779
- statusCode = 200 /* OK */,
780
- metadata = {}
781
- } = 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;
782
1026
  const successInfo = getSuccessDefinition(statusCode);
783
1027
  const response = {
784
1028
  success: true,
785
1029
  status_code: successInfo.code
786
1030
  };
787
- if (shouldIncludeTimestamp()) {
1031
+ if (this.config.includeTimestamp ?? true) {
788
1032
  response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
789
1033
  }
790
- if (statusCode !== 204 /* NO_CONTENT */ && statusCode !== 205 /* RESET_CONTENT */) {
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
- const transformer = getResponseTransformer();
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
- * @param error - HttpError instance
811
- * @param config - Optional error response configuration
812
- * @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.
813
1060
  */
814
- static error(error, config = {}) {
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.message
1071
+ message: this.resolveMessage(error, exposed)
823
1072
  }
824
1073
  };
825
- if (shouldIncludeTimestamp()) {
1074
+ if (cfg.includeTimestamp ?? true) {
826
1075
  response.timestamp = (/* @__PURE__ */ new Date()).toISOString();
827
1076
  }
828
- if (includeStack ?? isDevelopment()) {
829
- response.error.details = error.stack;
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
- 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
+ }
836
1104
  }
837
1105
  if (additionalFields) {
838
- Object.assign(response, additionalFields);
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
- * Format a response from any error (converts to HttpError first)
848
- *
849
- * @param error - Any error type
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
- static fromError(error, config = {}) {
1121
+ problem(error, options = {}) {
854
1122
  const httpError = HttpError.fromError(error);
855
- return _HttpResponse.error(httpError, config);
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
- static ok(data, message) {
862
- return _HttpResponse.success({ data, message, statusCode: 200 /* OK */ });
1135
+ ok(data, message) {
1136
+ return this.success({ data, message, statusCode: 200 /* OK */ });
863
1137
  }
864
1138
  /** 201 Created */
865
- static created(data, message) {
866
- return _HttpResponse.success({ data, message, statusCode: 201 /* CREATED */ });
1139
+ created(data, message) {
1140
+ return this.success({ data, message, statusCode: 201 /* CREATED */ });
867
1141
  }
868
1142
  /** 202 Accepted */
869
- static accepted(data, message) {
870
- return _HttpResponse.success({ data, message, statusCode: 202 /* ACCEPTED */ });
1143
+ accepted(data, message) {
1144
+ return this.success({ data, message, statusCode: 202 /* ACCEPTED */ });
871
1145
  }
872
1146
  /** 204 No Content */
873
- static noContent() {
874
- return _HttpResponse.success({ statusCode: 204 /* NO_CONTENT */ });
1147
+ noContent() {
1148
+ return this.success({ statusCode: 204 /* NO_CONTENT */ });
875
1149
  }
876
1150
  /** 206 Partial Content */
877
- static partialContent(data, message) {
878
- return _HttpResponse.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
1151
+ partialContent(data, message) {
1152
+ return this.success({ data, message, statusCode: 206 /* PARTIAL_CONTENT */ });
879
1153
  }
880
1154
  /** 304 Not Modified */
881
- static notModified() {
882
- return _HttpResponse.success({ statusCode: 304 /* NOT_MODIFIED */ });
1155
+ notModified() {
1156
+ return this.success({ statusCode: 304 /* NOT_MODIFIED */ });
883
1157
  }
884
1158
  // ========================================================================
885
- // Utility Methods
1159
+ // Pagination
886
1160
  // ========================================================================
887
- /**
888
- * Check if a response is a success response
889
- */
890
- static isSuccess(response) {
891
- return response.success === true;
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: pagination.limit,
1171
+ limit: effectiveLimit,
910
1172
  total: pagination.total,
911
- total_pages: pagination.totalPages ?? Math.ceil(pagination.total / pagination.limit),
912
- has_next: pagination.page < (pagination.totalPages ?? Math.ceil(pagination.total / pagination.limit)),
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
- // Annotate the CommonJS export names for ESM import in node:
920
- 0 && (module.exports = {
921
- HttpClientErrorCode,
922
- HttpError,
923
- HttpErrorCode,
924
- HttpErrorDefinitions,
925
- HttpInfoCode,
926
- HttpInfoDefinitions,
927
- HttpRedirectCode,
928
- HttpRedirectDefinitions,
929
- HttpResponse,
930
- HttpServerErrorCode,
931
- HttpSuccessCode,
932
- HttpSuccessDefinitions,
933
- configure,
934
- getConfig,
935
- getErrorDefinition,
936
- getSuccessDefinition,
937
- isDevelopment,
938
- resetConfig
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