@bombillazo/error-x 0.5.0 → 0.6.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/README.md CHANGED
@@ -18,6 +18,7 @@ A smart, isomorphic, and type-safe error library for TypeScript applications. Pr
18
18
  - **Custom metadata** with type-safe generics for additional context
19
19
  - **Global configuration** for stack cleaning and defaults
20
20
  - **Serialization/deserialization** for network transfer and storage
21
+ - **ErrorXResolver** for i18n, documentation URLs, and custom presentation logic
21
22
  - **Custom ErrorX class** examples:
22
23
  - `HTTPErrorX` - HTTP status code presets (400-511)
23
24
  - `DBErrorX` - Database error presets (connection, query, constraints)
@@ -60,7 +61,6 @@ throw new ErrorX({
60
61
  message: "User authentication failed",
61
62
  name: "AuthError",
62
63
  code: "AUTH_FAILED",
63
- uiMessage: "Please check your credentials",
64
64
  httpStatus: 401,
65
65
  metadata: { userId: 123 },
66
66
  });
@@ -87,20 +87,19 @@ The base error class that extends the native `Error` with enhanced capabilities.
87
87
 
88
88
  #### Properties
89
89
 
90
- | Property | Type | Description |
91
- | ------------ | -------------------------- | -------------------------------------------------------------- |
92
- | `message` | `string` | Technical error message |
93
- | `name` | `string` | Error type/name |
94
- | `code` | `string` | Error identifier code (auto-generated from name if not set) |
95
- | `uiMessage` | `string \| undefined` | User-friendly message for UI display |
96
- | `httpStatus` | `number \| undefined` | HTTP status code associated with this error |
97
- | `metadata` | `TMetadata \| undefined` | Additional context (type-safe with generics) |
98
- | `timestamp` | `number` | Unix epoch timestamp (ms) when error was created |
99
- | `stack` | `string \| undefined` | Stack trace (inherited from Error) |
100
- | `chain` | `readonly ErrorX[]` | Full error sequence: `[this, parent, grandparent, ..., root]` |
101
- | `root` | `ErrorX \| undefined` | Error that started the whole error chain |
102
- | `parent` | `ErrorX \| undefined` | Error that immediately precedes this error in the chain |
103
- | `original` | `ErrorXCause \| undefined` | Stores the original non-ErrorX error used to create this error |
90
+ | Property | Type | Description |
91
+ | ------------ | ----------------------------- | -------------------------------------------------------------- |
92
+ | `message` | `string` | Technical error message |
93
+ | `name` | `string` | Error type/name |
94
+ | `code` | `string` | Error identifier code (auto-generated from name if not set) |
95
+ | `httpStatus` | `number \| undefined` | HTTP status code associated with this error |
96
+ | `metadata` | `TMetadata \| undefined` | Additional context (type-safe with generics) |
97
+ | `timestamp` | `number` | Unix epoch timestamp (ms) when error was created |
98
+ | `stack` | `string \| undefined` | Stack trace (inherited from Error) |
99
+ | `chain` | `readonly ErrorX[]` | Full error sequence: `[this, parent, grandparent, ..., root]` |
100
+ | `root` | `ErrorX \| undefined` | Error that started the whole error chain |
101
+ | `parent` | `ErrorX \| undefined` | Error that immediately precedes this error in the chain |
102
+ | `original` | `ErrorXSnapshot \| undefined` | Stores the original non-ErrorX error used to create this error |
104
103
 
105
104
  #### Static Methods
106
105
 
@@ -143,7 +142,6 @@ new ErrorX({
143
142
  message: "User not found",
144
143
  name: "NotFoundError",
145
144
  code: "USER_NOT_FOUND",
146
- uiMessage: "The requested user does not exist",
147
145
  httpStatus: 404,
148
146
  metadata: { userId: 123 },
149
147
  });
@@ -158,15 +156,78 @@ new ErrorX<UserMeta>({
158
156
 
159
157
  ### ErrorXOptions
160
158
 
161
- | Property | Type | Default | Description |
162
- | ---------- | --------------------------------- | --------------------- | ----------------------------------------- |
163
- | message | `string` | `'An error occurred'` | Technical error message |
164
- | name | `string` | `'Error'` | Error type/name |
165
- | code | `string \| number` | Auto-generated | Error identifier (UPPER_SNAKE_CASE) |
166
- | uiMessage | `string` | `undefined` | User-friendly message |
167
- | httpStatus | `number` | `undefined` | HTTP status code |
168
- | metadata | `TMetadata` | `undefined` | Additional context |
169
- | cause | `ErrorXCause \| Error \| unknown` | `undefined` | Error that caused this (builds the chain) |
159
+ | Property | Type | Default | Description |
160
+ | ---------- | ------------------ | --------------------- | ----------------------------------------- |
161
+ | message | `string` | `'An error occurred'` | Technical error message |
162
+ | name | `string` | `'Error'` | Error type/name |
163
+ | code | `string \| number` | Auto-generated | Error identifier (UPPER_SNAKE_CASE) |
164
+ | httpStatus | `number` | `undefined` | HTTP status code |
165
+ | metadata | `TMetadata` | `undefined` | Additional context |
166
+ | cause | `unknown` | `undefined` | Error that caused this (builds the chain) |
167
+
168
+ ## Global Configuration
169
+
170
+ Configure stack trace cleaning and other global settings.
171
+
172
+ ```typescript
173
+ import { ErrorX } from "@bombillazo/error-x";
174
+
175
+ // Enable stack cleaning with custom delimiter
176
+ ErrorX.configure({
177
+ cleanStack: true,
178
+ cleanStackDelimiter: "my-app-entry",
179
+ });
180
+
181
+ // Custom patterns to remove from stack traces
182
+ ErrorX.configure({
183
+ cleanStack: ["node_modules", "internal/"],
184
+ });
185
+
186
+ // Disable stack cleaning
187
+ ErrorX.configure({ cleanStack: false });
188
+
189
+ // Get current config
190
+ const config = ErrorX.getConfig();
191
+
192
+ // Reset to defaults
193
+ ErrorX.resetConfig();
194
+ ```
195
+
196
+ ### Auto Code Generation
197
+
198
+ Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
199
+
200
+ ```typescript
201
+ new ErrorX({ name: "DatabaseError" });
202
+ // → code: 'DATABASE_ERROR'
203
+
204
+ new ErrorX({ name: "userAuthError" });
205
+ // → code: 'USER_AUTH_ERROR'
206
+
207
+ new ErrorX({ name: "API Timeout" });
208
+ // → code: 'API_TIMEOUT'
209
+ ```
210
+
211
+ ### Message Formatting
212
+
213
+ ErrorX does NOT auto-format messages. Messages are passed through as-is:
214
+
215
+ ```typescript
216
+ new ErrorX({ message: "test error" });
217
+ // → message: 'test error'
218
+
219
+ new ErrorX({ message: "Test error." });
220
+ // → message: 'Test error.'
221
+ ```
222
+
223
+ Empty or whitespace-only messages default to `'An error occurred'`:
224
+
225
+ ```typescript
226
+ new ErrorX({ message: "" });
227
+ // → message: 'An error occurred'
228
+ ```
229
+
230
+ Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
170
231
 
171
232
  ---
172
233
 
@@ -247,7 +308,7 @@ Serialize ErrorX instances for network transfer or storage.
247
308
  ```typescript
248
309
  // Serialize
249
310
  const json = error.toJSON();
250
- // { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
311
+ // { name, message, code, stack, metadata, timestamp, httpStatus, original, chain }
251
312
 
252
313
  // Deserialize
253
314
  const restored = ErrorX.fromJSON(json);
@@ -288,7 +349,7 @@ try {
288
349
  if (ErrorX.isErrorX(error)) {
289
350
  console.log(
290
351
  "Error chain:",
291
- error.chain.map((e) => e.name)
352
+ error.chain.map((e) => e.name),
292
353
  );
293
354
  console.log("Root cause:", error.root?.original);
294
355
  }
@@ -410,7 +471,6 @@ try {
410
471
 
411
472
  // With overrides
412
473
  ValidationErrorX.fromZodError(zodError, {
413
- uiMessage: "Please check your input",
414
474
  httpStatus: 422,
415
475
  });
416
476
 
@@ -455,25 +515,29 @@ const paymentPresets = {
455
515
  code: "INSUFFICIENT_FUNDS",
456
516
  name: "PaymentError",
457
517
  message: "Insufficient funds.",
458
- uiMessage: "Your payment method has insufficient funds.",
459
518
  httpStatus: 402,
460
519
  },
461
520
  CARD_DECLINED: {
462
521
  code: "CARD_DECLINED",
463
522
  name: "PaymentError",
464
523
  message: "Card declined.",
465
- uiMessage: "Your card was declined. Please try another payment method.",
466
524
  httpStatus: 402,
467
525
  },
468
526
  EXPIRED_CARD: {
469
527
  code: "EXPIRED_CARD",
470
528
  name: "PaymentError",
471
529
  message: "Card expired.",
472
- uiMessage: "Your card has expired. Please update your payment method.",
473
530
  httpStatus: 402,
474
531
  },
475
532
  } as const satisfies Record<string, ErrorXOptions>;
476
533
 
534
+ // Optional: Define user-friendly messages separately
535
+ export const paymentErrorUiMessages: Record<keyof typeof paymentPresets, string> = {
536
+ INSUFFICIENT_FUNDS: "Your payment method has insufficient funds.",
537
+ CARD_DECLINED: "Your card was declined. Please try another payment method.",
538
+ EXPIRED_CARD: "Your card has expired. Please update your payment method.",
539
+ };
540
+
477
541
  // 3. Derive preset key type
478
542
  type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
479
543
 
@@ -492,12 +556,12 @@ export class PaymentErrorX extends ErrorX<PaymentMetadata> {
492
556
  // Override create for proper typing
493
557
  static override create(
494
558
  presetKey?: PaymentPresetKey,
495
- overrides?: Partial<ErrorXOptions<PaymentMetadata>>
559
+ overrides?: Partial<ErrorXOptions<PaymentMetadata>>,
496
560
  ): PaymentErrorX {
497
561
  return ErrorX.create.call(
498
562
  PaymentErrorX,
499
563
  presetKey,
500
- overrides
564
+ overrides,
501
565
  ) as PaymentErrorX;
502
566
  }
503
567
  }
@@ -516,73 +580,155 @@ if (error instanceof PaymentErrorX) {
516
580
 
517
581
  ---
518
582
 
519
- ## Other Usage
583
+ ## ErrorXResolver
520
584
 
521
- ### Global Configuration
585
+ The `ErrorXResolver` class resolves `ErrorX` instances to enhanced presentation objects with i18n support, documentation URLs, and custom properties.
522
586
 
523
- Configure stack trace cleaning and other global settings.
587
+ ### Basic Usage
524
588
 
525
589
  ```typescript
526
- import { ErrorX } from "@bombillazo/error-x";
590
+ import { ErrorXResolver, HTTPErrorX } from "@bombillazo/error-x";
527
591
 
528
- // Enable stack cleaning with custom delimiter
529
- ErrorX.configure({
530
- cleanStack: true,
531
- cleanStackDelimiter: "my-app-entry",
592
+ const resolver = new ErrorXResolver({
593
+ // Required: determine error type from error instance
594
+ onResolveType: (error) => {
595
+ if (error instanceof HTTPErrorX) return "http";
596
+ return "general";
597
+ },
598
+ // Per-type configuration
599
+ configs: {
600
+ http: { namespace: "errors.http" },
601
+ general: { namespace: "errors" },
602
+ },
532
603
  });
533
604
 
534
- // Custom patterns to remove from stack traces
535
- ErrorX.configure({
536
- cleanStack: ["node_modules", "internal/"],
537
- });
605
+ const error = HTTPErrorX.create(404);
606
+ const result = resolver.resolve(error);
607
+ // → { uiMessage: undefined, docsUrl: '', i18nKey: 'errors.http.NOT_FOUND', errorType: 'http', config: {...} }
608
+ ```
538
609
 
539
- // Disable stack cleaning
540
- ErrorX.configure({ cleanStack: false });
610
+ ### With i18n Integration
541
611
 
542
- // Get current config
543
- const config = ErrorX.getConfig();
612
+ ```typescript
613
+ import i18next from "i18next";
544
614
 
545
- // Reset to defaults
546
- ErrorX.resetConfig();
615
+ const resolver = new ErrorXResolver({
616
+ i18n: {
617
+ resolver: (key, params) => i18next.t(key, params),
618
+ keyTemplate: "{namespace}.{code}", // default
619
+ },
620
+ docs: {
621
+ baseUrl: "https://docs.example.com/errors",
622
+ },
623
+ onResolveType: (error) => (error.code.startsWith("HTTP_") ? "http" : "general"),
624
+ configs: {
625
+ http: { namespace: "errors.http", docsPath: "/http" },
626
+ general: { namespace: "errors.general" },
627
+ },
628
+ });
629
+
630
+ const result = resolver.resolve(error);
631
+ // → { uiMessage: 'Not found', docsUrl: 'https://docs.example.com/errors/http#NOT_FOUND', ... }
547
632
  ```
548
633
 
549
- ### Auto Code Generation
634
+ ### Custom Config Properties
550
635
 
551
- Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
636
+ Extend the resolver with custom properties for your domain:
552
637
 
553
638
  ```typescript
554
- new ErrorX({ name: "DatabaseError" });
555
- // → code: 'DATABASE_ERROR'
639
+ import { ErrorXResolver, type ErrorXResolverConfig } from "@bombillazo/error-x";
640
+
641
+ // Define custom config with additional properties
642
+ type MyConfig = ErrorXResolverConfig<{
643
+ severity: "error" | "warning" | "info";
644
+ retryable: boolean;
645
+ }>;
646
+
647
+ const resolver = new ErrorXResolver<MyConfig>({
648
+ onResolveType: (error) => "api",
649
+ defaults: {
650
+ namespace: "errors",
651
+ severity: "error",
652
+ retryable: false,
653
+ },
654
+ configs: {
655
+ api: {
656
+ namespace: "errors.api",
657
+ severity: "warning",
658
+ retryable: true,
659
+ // Per-code overrides
660
+ presets: {
661
+ NOT_FOUND: { severity: "info", retryable: false },
662
+ },
663
+ },
664
+ },
665
+ });
666
+ ```
556
667
 
557
- new ErrorX({ name: "userAuthError" });
558
- // → code: 'USER_AUTH_ERROR'
668
+ ### Custom Result Type
559
669
 
560
- new ErrorX({ name: "API Timeout" });
561
- // → code: 'API_TIMEOUT'
670
+ Transform the resolve output to match your API:
671
+
672
+ ```typescript
673
+ type MyResult = { message: string; docs: string; canRetry: boolean };
674
+
675
+ const resolver = new ErrorXResolver<MyConfig, MyResult>({
676
+ onResolveType: (error) => "api",
677
+ onResolve: (error, context) => ({
678
+ message: context.uiMessage ?? error.message,
679
+ docs: context.docsUrl,
680
+ canRetry: context.config.retryable,
681
+ }),
682
+ configs: { api: { namespace: "errors.api", retryable: true } },
683
+ });
562
684
  ```
563
685
 
564
- ### Message Formatting
686
+ ---
565
687
 
566
- ErrorX does NOT auto-format messages. Messages are passed through as-is:
688
+ ## UI Messages
689
+
690
+ User-friendly messages are provided separately from error presets. This allows errors to remain technical while UI messages can be managed independently (e.g., for i18n).
691
+
692
+ ### Available Exports
567
693
 
568
694
  ```typescript
569
- new ErrorX({ message: "test error" });
570
- // → message: 'test error'
695
+ import {
696
+ httpErrorUiMessages,
697
+ dbErrorUiMessages,
698
+ validationErrorUiMessage,
699
+ } from "@bombillazo/error-x";
571
700
 
572
- new ErrorX({ message: "Test error." });
573
- // message: 'Test error.'
574
- ```
701
+ // HTTP error messages keyed by status code
702
+ httpErrorUiMessages[404]; // "The requested resource could not be found."
703
+ httpErrorUiMessages[500]; // "An unexpected error occurred. Please try again later."
575
704
 
576
- Empty or whitespace-only messages default to `'An error occurred'`:
705
+ // Database error messages keyed by preset name
706
+ dbErrorUiMessages.CONNECTION_FAILED; // "Unable to connect to the database. Please try again later."
707
+ dbErrorUiMessages.UNIQUE_VIOLATION; // "This record already exists."
577
708
 
578
- ```typescript
579
- new ErrorX({ message: "" });
580
- // → message: 'An error occurred'
709
+ // Validation error default message
710
+ validationErrorUiMessage; // "The provided input is invalid. Please check your data."
581
711
  ```
582
712
 
583
- Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
713
+ ### Usage with ErrorXResolver
584
714
 
585
- ---
715
+ ```typescript
716
+ import { ErrorXResolver, HTTPErrorX, httpErrorUiMessages } from "@bombillazo/error-x";
717
+
718
+ const resolver = new ErrorXResolver({
719
+ onResolveType: () => "http",
720
+ configs: {
721
+ http: {
722
+ namespace: "errors.http",
723
+ presets: {
724
+ // Use provided UI messages as static fallbacks
725
+ NOT_FOUND: { uiMessage: httpErrorUiMessages[404] },
726
+ UNAUTHORIZED: { uiMessage: httpErrorUiMessages[401] },
727
+ },
728
+ },
729
+ },
730
+ });
731
+ ```
586
732
 
587
733
  ## License
588
734