error-message-utils 1.2.12 → 1.2.14

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/README.md CHANGED
@@ -6,6 +6,7 @@ consistent shape:
6
6
  - a readable message
7
7
  - a stable error code
8
8
  - optional extra data
9
+ - an optional native error cause
9
10
 
10
11
  Use `Exception` for most application code. Use `encodeError` and `decodeError` when you need to
11
12
  send or receive an error code inside a plain string.
@@ -18,7 +19,8 @@ npm i -S error-message-utils
18
19
 
19
20
  ## Recommended Usage: Exception
20
21
 
21
- `Exception` is an `Error` subclass that stores a normalized message, a code, and optional data.
22
+ `Exception` is an `Error` subclass that stores a normalized message, a code, optional data, and an
23
+ optional native cause.
22
24
 
23
25
  ```typescript
24
26
  import { Exception } from 'error-message-utils';
@@ -37,6 +39,7 @@ exception.name; // 'Exception'
37
39
  exception.message; // 'Request failed'
38
40
  exception.code; // 'REQUEST_FAILED'
39
41
  exception.data; // null
42
+ exception.cause; // undefined
40
43
  exception.toString(); // 'Request failed{(REQUEST_FAILED)}'
41
44
  exception.toRecord();
42
45
  // {
@@ -46,29 +49,51 @@ exception.toRecord();
46
49
  // }
47
50
  ```
48
51
 
49
- ### Wrap Unknown Errors
52
+ ### Preserve an Error Cause
50
53
 
51
- When you catch an unknown error, pass it to `Exception`. The package will extract the best message
52
- and code it can find.
54
+ When wrapping an error, provide a stable contextual message and pass the original value through the
55
+ fourth `IErrorOptions` argument. This keeps the top-level message predictable while preserving the
56
+ original error for debugging.
53
57
 
54
58
  ```typescript
55
- import { Exception } from 'error-message-utils';
59
+ import { Exception, extractMessage } from 'error-message-utils';
56
60
 
57
61
  try {
58
62
  await sendReceiptEmail();
59
- } catch (error) {
60
- throw new Exception(error, 'RECEIPT_EMAIL_FAILED', {
61
- operation: 'sendReceiptEmail',
62
- });
63
+ } catch (cause) {
64
+ const exception = new Exception(
65
+ 'Unable to send the receipt email.',
66
+ 'RECEIPT_EMAIL_FAILED',
67
+ { operation: 'sendReceiptEmail' },
68
+ { cause },
69
+ );
70
+
71
+ exception.message; // 'Unable to send the receipt email.'
72
+ exception.cause === cause; // true
73
+ extractMessage(exception); // combines the message with a truthy cause chain
74
+
75
+ throw exception;
63
76
  }
64
77
  ```
65
78
 
79
+ `IErrorOptions` is a direct alias of the native `ErrorOptions` type. This API requires the ES2022
80
+ TypeScript `lib` and a runtime that supports `Error` causes.
81
+
82
+ The existing `new Exception(error, code, data)` form remains supported. It normalizes the first
83
+ argument into the exception message but does not infer or store that argument as `cause`. Avoid
84
+ also appending the cause message to the top-level message because `extractMessage` already combines
85
+ truthy cause chains. Native `Error` semantics still retain falsy causes such as `false`, `0`, an
86
+ empty string, or `null`, but `extractMessage` does not append them.
87
+
88
+ `toString()` and `toRecord()` intentionally omit `cause`. A cause can contain internal or sensitive
89
+ details, so inspect or expose it only at an appropriate boundary.
90
+
66
91
  ### Extend Exception
67
92
 
68
93
  Create small domain-specific exception classes when your app has a stable set of error codes.
69
94
 
70
95
  ```typescript
71
- import { Exception } from 'error-message-utils';
96
+ import { Exception, type IErrorOptions } from 'error-message-utils';
72
97
 
73
98
  const USER_ERROR_CODES = {
74
99
  EmailTaken: 'USER_EMAIL_TAKEN',
@@ -78,8 +103,13 @@ const USER_ERROR_CODES = {
78
103
  type IUserErrorCode = (typeof USER_ERROR_CODES)[keyof typeof USER_ERROR_CODES];
79
104
 
80
105
  export class UserException extends Exception {
81
- public constructor(message: string, code: IUserErrorCode, data?: unknown) {
82
- super(message, code, data);
106
+ public constructor(
107
+ message: string,
108
+ code: IUserErrorCode,
109
+ data?: unknown,
110
+ options?: IErrorOptions,
111
+ ) {
112
+ super(message, code, data, options);
83
113
  this.name = 'UserException';
84
114
  }
85
115
  }
@@ -133,6 +163,29 @@ if (!result.success) {
133
163
  }
134
164
  ```
135
165
 
166
+ ### Redact Sensitive Values
167
+
168
+ `extractRedactedMessage` extracts a message using the same rules as `extractMessage`, then replaces
169
+ every exact sensitive value with `[redacted]`.
170
+
171
+ ```typescript
172
+ import { extractRedactedMessage } from 'error-message-utils';
173
+
174
+ const error = new Error('Request failed for token abc123.', {
175
+ cause: new Error('The provider rejected abc123.'),
176
+ });
177
+
178
+ extractRedactedMessage(error, ['abc123']);
179
+ // 'Request failed for token [redacted].; [CAUSE]: The provider rejected [redacted].'
180
+
181
+ extractRedactedMessage(error, ['different-value']);
182
+ // 'Request failed for token abc123.; [CAUSE]: The provider rejected abc123.'
183
+ ```
184
+
185
+ Matching is literal and case-sensitive. Empty values are ignored. The function does not
186
+ automatically identify secrets, so transformed or encoded versions must be supplied separately if
187
+ they also need redaction.
188
+
136
189
  ### Get an Error Code
137
190
 
138
191
  Use `getErrorCode` when you only need the resolved code.
@@ -232,6 +285,7 @@ import {
232
285
  decodeError,
233
286
  encodeError,
234
287
  extractMessage,
288
+ extractRedactedMessage,
235
289
  getErrorCode,
236
290
  hasErrorCode,
237
291
  hasErrorCodePrefix,
@@ -240,6 +294,7 @@ import {
240
294
  type IDecodedError,
241
295
  type IErrorCode,
242
296
  type IErrorCodeCarrier,
297
+ type IErrorOptions,
243
298
  type IExceptionRecord,
244
299
  } from 'error-message-utils';
245
300
  ```
@@ -248,13 +303,14 @@ import {
248
303
 
249
304
  | Export | Description |
250
305
  | --- | --- |
251
- | `Exception` | An `Error` subclass that normalizes an unknown error into `message`, `code`, and `data`. It can also serialize itself with `toString()` or `toRecord()`. |
306
+ | `Exception` | An `Error` subclass that normalizes an unknown error into `message`, `code`, and `data`, and accepts native error options such as `cause`. It can also serialize itself with `toString()` or `toRecord()`, which omit the cause. |
252
307
 
253
308
  ### Functions
254
309
 
255
310
  | Export | Signature | Description |
256
311
  | --- | --- | --- |
257
312
  | `extractMessage` | `(error: unknown) => string` | Extracts the best readable message from strings, `Error` instances, nested error-like objects, `Error.cause` chains, arrays, plain objects, and Zod errors. Returns `DEFAULT_MESSAGE` when no useful message can be extracted. |
313
+ | `extractRedactedMessage` | `(error: unknown, sensitiveValues: readonly string[]) => string` | Extracts a message and replaces every exact, case-sensitive occurrence of each non-empty sensitive value with `[redacted]`. Returns the extracted message unchanged when no value matches. |
258
314
  | `encodeError` | `(error: unknown, code: IErrorCode) => string` | Extracts a message from `error` and appends the wrapped code at the end of the message. |
259
315
  | `decodeError` | `(error: unknown) => IDecodedError` | Extracts a message, resolves a code from an encoded message or code-carrying object, and returns `{ message, code, data }`. |
260
316
  | `isEncodedError` | `(error: unknown) => boolean` | Returns `true` when `decodeError(error).code` resolves to a non-default code. |
@@ -268,6 +324,8 @@ import {
268
324
  ```typescript
269
325
  type IErrorCode = string | number;
270
326
 
327
+ type IErrorOptions = ErrorOptions;
328
+
271
329
  type IDecodedError = {
272
330
  message: string;
273
331
  code: IErrorCode;
@@ -288,9 +346,10 @@ type IExceptionRecord = {
288
346
  | Export | Description |
289
347
  | --- | --- |
290
348
  | `IErrorCode` | The supported type for application error codes. |
349
+ | `IErrorOptions` | A direct alias of the native `ErrorOptions` type accepted by the `Exception` constructor. |
291
350
  | `IDecodedError` | The object returned by `decodeError`. |
292
351
  | `IErrorCodeCarrier` | A plain object shape that can provide a code to `decodeError`, `getErrorCode`, `hasErrorCode`, `hasErrorCodePrefix`, and `Exception`. |
293
- | `IExceptionRecord` | The serializable object returned by `Exception.toRecord()`. |
352
+ | `IExceptionRecord` | The serializable object returned by `Exception.toRecord()`. It does not include `cause`. |
294
353
 
295
354
  ### Constants
296
355
 
@@ -6,6 +6,13 @@ import type { IErrorCode, IDecodedError } from '../shared/types.js';
6
6
  * @returns A string containing the extracted message or the default message if extraction fails.
7
7
  */
8
8
  export declare const extractMessage: (error: any) => string;
9
+ /**
10
+ * Extracts an error message and replaces exact sensitive values with a redaction marker.
11
+ * @param error The error to extract the message from.
12
+ * @param sensitiveValues The exact case-sensitive values to redact.
13
+ * @returns The extracted message with matching sensitive values redacted.
14
+ */
15
+ export declare const extractRedactedMessage: (error: unknown, sensitiveValues: string[]) => string;
9
16
  /**
10
17
  * Encoding / Decoding
11
18
  */
@@ -1 +1 @@
1
- import{ZodError}from"zod";import{DEFAULT_CODE,DEFAULT_MESSAGE}from"../shared/constants.js";import{wrapCode,unwrapCode}from"../utils/index.js";import{extractZodErrorMessage,getDecodedErrorCode}from"./utilities.js";const __extractMessage=(r,e)=>{if("string"==typeof r&&r.length)return r;if(r instanceof ZodError)return extractZodErrorMessage(r);if(r&&"object"==typeof r){if(e.has(r))return DEFAULT_MESSAGE;e.add(r)}if(r instanceof Error&&r.message)return r.cause?`${r.message}; [CAUSE]: ${__extractMessage(r.cause,e)}`:r.message;if(r&&"object"==typeof r){if(r.message)return __extractMessage(r.message,e);if(r.msg)return __extractMessage(r.msg,e);if(r.error)return __extractMessage(r.error,e);if(r.err)return __extractMessage(r.err,e);if(r.errors)return __extractMessage(r.errors,e);if(r.errs)return __extractMessage(r.errs,e);if(r.reason)return __extractMessage(r.reason,e);if(r.reasons)return __extractMessage(r.reasons,e);if(r.issue)return __extractMessage(r.issue,e);if(r.issues)return __extractMessage(r.issues,e);if(r.data)return __extractMessage(r.data,e);try{return JSON.stringify(r)}catch(e){console.error("Error during extractMessage:"),console.error("Original Error: ",r),console.error("JSON.stringify Error:",e)}}return DEFAULT_MESSAGE};export const extractMessage=r=>__extractMessage(r,new WeakSet);export const encodeError=(r,e)=>`${extractMessage(r)}${wrapCode(e)}`;export const decodeError=r=>{const e=extractMessage(r),{code:t,startsAt:s}=unwrapCode(e);return{message:s>0?e.slice(0,s):e,code:getDecodedErrorCode(r,t),data:null!==r&&"object"==typeof r&&"data"in r?r.data:null}};export const isEncodedError=r=>decodeError(r).code!==DEFAULT_CODE;export const getErrorCode=r=>{const{code:e}=decodeError(r);return e!==DEFAULT_CODE?e:null};export const hasErrorCodePrefix=(r,e)=>{if(0===e.length)return!1;const t=getErrorCode(r)??r;return"string"==typeof t&&t.startsWith(e)};export const hasErrorCode=(r,e)=>null!==r&&(r===e||decodeError(r).code===e);export const isDefaultErrorMessage=(r,e=!1)=>e?r===DEFAULT_MESSAGE:"string"==typeof r&&r.includes(DEFAULT_MESSAGE);
1
+ import{ZodError}from"zod";import{DEFAULT_CODE,DEFAULT_MESSAGE}from"../shared/constants.js";import{wrapCode,unwrapCode}from"../utils/index.js";import{extractZodErrorMessage,getDecodedErrorCode,redactSensitiveValues}from"./utilities.js";const __extractMessage=(e,r)=>{if("string"==typeof e&&e.length)return e;if(e instanceof ZodError)return extractZodErrorMessage(e);if(e&&"object"==typeof e){if(r.has(e))return DEFAULT_MESSAGE;r.add(e)}if(e instanceof Error&&e.message)return e.cause?`${e.message}; [CAUSE]: ${__extractMessage(e.cause,r)}`:e.message;if(e&&"object"==typeof e){if(e.message)return __extractMessage(e.message,r);if(e.msg)return __extractMessage(e.msg,r);if(e.error)return __extractMessage(e.error,r);if(e.err)return __extractMessage(e.err,r);if(e.errors)return __extractMessage(e.errors,r);if(e.errs)return __extractMessage(e.errs,r);if(e.reason)return __extractMessage(e.reason,r);if(e.reasons)return __extractMessage(e.reasons,r);if(e.issue)return __extractMessage(e.issue,r);if(e.issues)return __extractMessage(e.issues,r);if(e.data)return __extractMessage(e.data,r);try{return JSON.stringify(e)}catch{return DEFAULT_MESSAGE}}return DEFAULT_MESSAGE};export const extractMessage=e=>__extractMessage(e,new WeakSet);export const extractRedactedMessage=(e,r)=>redactSensitiveValues(extractMessage(e),r);export const encodeError=(e,r)=>`${extractMessage(e)}${wrapCode(r)}`;export const decodeError=e=>{const r=extractMessage(e),{code:t,startsAt:s}=unwrapCode(r);return{message:s>0?r.slice(0,s):r,code:getDecodedErrorCode(e,t),data:null!==e&&"object"==typeof e&&"data"in e?e.data:null}};export const isEncodedError=e=>decodeError(e).code!==DEFAULT_CODE;export const getErrorCode=e=>{const{code:r}=decodeError(e);return r!==DEFAULT_CODE?r:null};export const hasErrorCodePrefix=(e,r)=>{if(0===r.length)return!1;const t=getErrorCode(e)??e;return"string"==typeof t&&t.startsWith(r)};export const hasErrorCode=(e,r)=>null!==e&&(e===r||decodeError(e).code===r);export const isDefaultErrorMessage=(e,r=!1)=>r?e===DEFAULT_MESSAGE:"string"==typeof e&&e.includes(DEFAULT_MESSAGE);
@@ -1,5 +1,12 @@
1
1
  import { ZodError } from 'zod';
2
2
  import { IErrorCode } from '../shared/types.js';
3
+ /**
4
+ * Replaces every literal sensitive value in a message with the redaction marker.
5
+ * @param message The message to redact.
6
+ * @param sensitiveValues The exact case-sensitive values to redact.
7
+ * @returns The message with matching sensitive values redacted.
8
+ */
9
+ export declare const redactSensitiveValues: (message: string, sensitiveValues: string[]) => string;
3
10
  /**
4
11
  * Attempts to extract a Zod error message from a ZodError instance. If unable to do so, it returns
5
12
  * the default error message.
@@ -1 +1 @@
1
- import{DEFAULT_CODE,DEFAULT_MESSAGE}from"../shared/constants.js";const __extractPathFromZodError=r=>r&&Array.isArray(r.issues)&&r.issues.length&&Array.isArray(r.issues[0].path)&&r.issues[0].path.length?r.issues[0].path.join("."):"Unknown path";export const extractZodErrorMessage=r=>r&&Array.isArray(r.issues)&&r.issues.length&&Array.isArray(r.issues[0].path)&&r.issues[0].message?`${r.issues[0].message} (${__extractPathFromZodError(r)})`:DEFAULT_MESSAGE;const __isErrorCodeCarrier=r=>"object"==typeof r&&null!==r&&"code"in r&&("string"==typeof r.code||"number"==typeof r.code);export const getDecodedErrorCode=(r,s)=>s!==DEFAULT_CODE?s:__isErrorCodeCarrier(r)?r.code:DEFAULT_CODE;
1
+ import{DEFAULT_CODE,DEFAULT_MESSAGE}from"../shared/constants.js";const REDACTION_REPLACEMENT="[redacted]",__collectSensitiveRanges=(e,r)=>{const s=[];return r.forEach((r=>{let t=e.indexOf(r);for(;-1!==t;)s.push({start:t,end:t+r.length}),t=e.indexOf(r,t+1)})),s},__mergeOverlappingRanges=e=>{const r=[...e].sort(((e,r)=>e.start-r.start||r.end-e.end)),s=[];return r.forEach((e=>{const r=s.at(-1);!r||e.start>=r.end?s.push({...e}):e.end>r.end&&(r.end=e.end)})),s};export const redactSensitiveValues=(e,r)=>{const s=[...new Set(r.filter((e=>e.length>0)))];if(!s.length)return e;const t=__mergeOverlappingRanges(__collectSensitiveRanges(e,s));if(!t.length)return e;const n=[];let o=0;return t.forEach((r=>{n.push(e.slice(o,r.start),"[redacted]"),o=r.end})),n.push(e.slice(o)),n.join("")};const __extractPathFromZodError=e=>e&&Array.isArray(e.issues)&&e.issues.length&&Array.isArray(e.issues[0].path)&&e.issues[0].path.length?e.issues[0].path.join("."):"Unknown path";export const extractZodErrorMessage=e=>e&&Array.isArray(e.issues)&&e.issues.length&&Array.isArray(e.issues[0].path)&&e.issues[0].message?`${e.issues[0].message} (${__extractPathFromZodError(e)})`:DEFAULT_MESSAGE;const __isErrorCodeCarrier=e=>"object"==typeof e&&null!==e&&"code"in e&&("string"==typeof e.code||"number"==typeof e.code);export const getDecodedErrorCode=(e,r)=>r!==DEFAULT_CODE?r:__isErrorCodeCarrier(e)?e.code:DEFAULT_CODE;
@@ -1,7 +1,7 @@
1
- import { type IErrorCode } from '../shared/types.js';
2
- import { type IExceptionRecord } from './types.js';
1
+ import type { IErrorCode } from '../shared/types.js';
2
+ import type { IErrorOptions, IExceptionRecord } from './types.js';
3
3
  /**
4
- * Error subclass that normalizes unknown errors into a message, code, and optional data payload.
4
+ * Error subclass that normalizes unknown errors and supports native error options.
5
5
  */
6
6
  export declare class Exception extends Error {
7
7
  readonly code: IErrorCode;
@@ -11,8 +11,9 @@ export declare class Exception extends Error {
11
11
  * @param error The unknown error or message to normalize.
12
12
  * @param code The optional code that overrides any decoded code.
13
13
  * @param data The optional data payload that overrides any decoded data.
14
+ * @param options The native error options, including an optional cause.
14
15
  */
15
- constructor(error: unknown, code?: IErrorCode, data?: unknown);
16
+ constructor(error: unknown, code?: IErrorCode, data?: unknown, options?: IErrorOptions);
16
17
  /**
17
18
  * Override the default toString method to return a formatted error message with the code.
18
19
  * @returns A string representation of the error with the code.
@@ -1 +1 @@
1
- import{decodeError,encodeError}from"../error-handler/index.js";export class Exception extends Error{code;data;constructor(e,r,t){const o=decodeError(e);super(o.message),this.name="Exception",this.code=r??o.code,this.data=void 0===t?o.data:t}toString(){return encodeError(this.message,this.code)}[Symbol.toPrimitive](e){return"string"===e||"default"===e?this.toString():null}toRecord(){return{message:this.message,code:this.code,data:this.data??null}}}
1
+ import{decodeError,encodeError}from"../error-handler/index.js";export class Exception extends Error{code;data;constructor(e,r,t,o){const s=decodeError(e);super(s.message,o),this.name="Exception",this.code=r??s.code,this.data=void 0===t?s.data:t}toString(){return encodeError(this.message,this.code)}[Symbol.toPrimitive](e){return"string"===e||"default"===e?this.toString():null}toRecord(){return{message:this.message,code:this.code,data:this.data??null}}}
@@ -1,2 +1,2 @@
1
- export type { IExceptionRecord } from './types.js';
1
+ export type { IErrorOptions, IExceptionRecord } from './types.js';
2
2
  export { Exception } from './exception.js';
@@ -1,4 +1,5 @@
1
- import { IErrorCode } from '../shared/types.js';
1
+ import type { IErrorCode } from '../shared/types.js';
2
+ export type IErrorOptions = ErrorOptions;
2
3
  export type IExceptionRecord = {
3
4
  message: string;
4
5
  code: IErrorCode;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export type { IErrorCode, IDecodedError, IErrorCodeCarrier } from './shared/types.js';
2
2
  export { DEFAULT_CODE, DEFAULT_MESSAGE } from './shared/constants.js';
3
- export { extractMessage, encodeError, decodeError, isEncodedError, getErrorCode, hasErrorCodePrefix, hasErrorCode, isDefaultErrorMessage, } from './error-handler/index.js';
4
- export { type IExceptionRecord, Exception } from './exception/index.js';
3
+ export { extractMessage, extractRedactedMessage, encodeError, decodeError, isEncodedError, getErrorCode, hasErrorCodePrefix, hasErrorCode, isDefaultErrorMessage, } from './error-handler/index.js';
4
+ export { type IErrorOptions, type IExceptionRecord, Exception } from './exception/index.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{DEFAULT_CODE,DEFAULT_MESSAGE}from"./shared/constants.js";export{extractMessage,encodeError,decodeError,isEncodedError,getErrorCode,hasErrorCodePrefix,hasErrorCode,isDefaultErrorMessage}from"./error-handler/index.js";export{Exception}from"./exception/index.js";
1
+ export{DEFAULT_CODE,DEFAULT_MESSAGE}from"./shared/constants.js";export{extractMessage,extractRedactedMessage,encodeError,decodeError,isEncodedError,getErrorCode,hasErrorCodePrefix,hasErrorCode,isDefaultErrorMessage}from"./error-handler/index.js";export{Exception}from"./exception/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-message-utils",
3
- "version": "1.2.12",
3
+ "version": "1.2.14",
4
4
  "description": "The error-message-utils package simplifies error management in your web applications and RESTful APIs. It ensures consistent and scalable handling of error messages, saving you time and effort. Moreover, it gives you the ability to assign custom error codes so all possible cases can be handled accordingly.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",