reslib 1.1.0 → 2.0.1

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 (60) hide show
  1. package/README.md +9 -5
  2. package/build/auth/index.js +2 -2
  3. package/build/countries/index.js +2 -2
  4. package/build/currency/index.js +2 -2
  5. package/build/currency/session.js +2 -2
  6. package/build/exception/index.d.ts +1908 -0
  7. package/build/exception/index.js +5 -0
  8. package/build/i18n/index.d.ts +12 -4
  9. package/build/i18n/index.js +2 -2
  10. package/build/inputFormatter/index.js +2 -2
  11. package/build/logger/index.js +2 -2
  12. package/build/resources/ResourcePaginationHelper.js +1 -1
  13. package/build/resources/decorators/index.js +1 -1
  14. package/build/resources/fields/index.d.ts +5 -5
  15. package/build/resources/fields/index.js +1 -1
  16. package/build/resources/index.d.ts +1 -1
  17. package/build/resources/index.js +3 -3
  18. package/build/translations/index.d.ts +36 -4
  19. package/build/translations/index.js +2 -2
  20. package/build/translations/validator.en.d.ts +69 -4
  21. package/build/translations/validator.en.js +2 -2
  22. package/build/types/index.d.ts +21 -0
  23. package/build/utils/date/dateHelper.js +2 -2
  24. package/build/utils/date/index.js +2 -2
  25. package/build/utils/index.d.ts +2 -2
  26. package/build/utils/index.js +3 -3
  27. package/build/utils/interpolate.js +1 -1
  28. package/build/utils/isTime.js +1 -1
  29. package/build/utils/numbers.js +2 -2
  30. package/build/utils/object.js +1 -1
  31. package/build/validator/errors/index.d.ts +299 -0
  32. package/build/validator/errors/index.js +1 -0
  33. package/build/validator/index.d.ts +1 -0
  34. package/build/validator/index.js +3 -3
  35. package/build/validator/rules/array.js +2 -2
  36. package/build/validator/rules/boolean.js +2 -2
  37. package/build/validator/rules/date.js +2 -2
  38. package/build/validator/rules/default.js +2 -2
  39. package/build/validator/rules/enum.js +2 -2
  40. package/build/validator/rules/file.js +2 -2
  41. package/build/validator/rules/format.d.ts +13 -13
  42. package/build/validator/rules/format.js +3 -3
  43. package/build/validator/rules/index.js +3 -3
  44. package/build/validator/rules/multiRules.d.ts +10 -10
  45. package/build/validator/rules/multiRules.js +2 -2
  46. package/build/validator/rules/numeric.d.ts +8 -8
  47. package/build/validator/rules/numeric.js +2 -2
  48. package/build/validator/rules/object.js +2 -2
  49. package/build/validator/rules/string.d.ts +6 -6
  50. package/build/validator/rules/string.js +2 -2
  51. package/build/validator/rules/target.d.ts +8 -8
  52. package/build/validator/rules/target.js +2 -2
  53. package/build/validator/rules.types.d.ts +167 -0
  54. package/build/validator/rules.types.js +1 -0
  55. package/build/validator/types.d.ts +832 -1286
  56. package/build/validator/validator.d.ts +554 -867
  57. package/build/validator/validator.js +2 -2
  58. package/lib/cjs/exception.js +1 -0
  59. package/lib/esm/exception.mjs +2 -0
  60. package/package.json +6 -1
@@ -0,0 +1,1908 @@
1
+ import { ValidatorBulkError, ValidatorClassError, ValidatorError } from '../validator/errors';
2
+ /**
3
+ * Hook function type for intercepting exceptions (e.g., for logging).
4
+ */
5
+ export type ExceptionHook = (exception: BaseException) => void;
6
+ /**
7
+ * Options for serializing the exception.
8
+ */
9
+ export interface SerializationOptions {
10
+ /** Include the stack trace in the output. Default: false (unless not in production) */
11
+ stack?: boolean;
12
+ /** Include the cause chain in the output. Default: true */
13
+ cause?: boolean;
14
+ /** Max depth to serialize nested causes. Default: 10 */
15
+ maxCauseDepth?: number;
16
+ }
17
+ /**
18
+ * Base application exception class.
19
+ * This class provides a standardized, serializable exception structure that can be
20
+ * safely returned from REST API endpoints, extended for domain-specific errors,
21
+ * and intercepted via hooks.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Basic usage
26
+ * throw new BaseException('User not found');
27
+ * ```
28
+ *
29
+ * @example
30
+ * // extending
31
+ * class PaymentException extends BaseException<{ transactionId: string }> {
32
+ * constructor(message: string, transactionId: string) {
33
+ * super(message, { details: { transactionId } });
34
+ * }
35
+ * }
36
+ */
37
+ export declare class BaseException<TDetails = unknown, TCause = unknown> extends Error {
38
+ /**
39
+ * The constant name identifier for this exception class.
40
+ */
41
+ static readonly NAME: "BaseException";
42
+ /**
43
+ * Unique marker property to identify BaseException instances (and subclasses)
44
+ * even after serialization/deserialization where instanceof check fails.
45
+ */
46
+ readonly __isBaseException = true;
47
+ /**
48
+ * Global hooks that run when any BaseException (or subclass) is instantiated.
49
+ */
50
+ private static hooks;
51
+ /**
52
+ * Application-specific error code (e.g., 'USER_NOT_FOUND').
53
+ */
54
+ code?: string;
55
+ /**
56
+ * HTTP status code associated with this exception.
57
+ */
58
+ statusCode?: number;
59
+ /**
60
+ * Additional structured details about the exception.
61
+ */
62
+ details?: TDetails;
63
+ /**
64
+ * The underlying error that caused this exception.
65
+ */
66
+ cause?: TCause;
67
+ /**
68
+ * Timestamp when the exception was created.
69
+ */
70
+ readonly timestamp: Date;
71
+ readonly success: boolean;
72
+ /**
73
+ * The validation error that caused this exception.
74
+ */
75
+ validationError?: ValidatorError | ValidatorClassError | ValidatorBulkError;
76
+ /**
77
+ * Creates a new BaseException instance.
78
+ *
79
+ * @param message - Human-readable error message describing what went wrong
80
+ * @param options - Optional configuration object containing metadata like code, statusCode, details, cause, and timestamp
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // Basic usage
85
+ * const error = new BaseException('User not found');
86
+ * ```
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // With error code and HTTP status
91
+ * const error = new BaseException('Invalid credentials', {
92
+ * code: 'AUTH_FAILED',
93
+ * statusCode: 401
94
+ * });
95
+ * ```
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // With typed details
100
+ * interface PaymentDetails {
101
+ * transactionId: string;
102
+ * amount: number;
103
+ * }
104
+ *
105
+ * const error = new BaseException<PaymentDetails>('Payment failed', {
106
+ * code: 'PAYMENT_ERROR',
107
+ * statusCode: 402,
108
+ * details: {
109
+ * transactionId: 'tx_12345',
110
+ * amount: 99.99
111
+ * }
112
+ * });
113
+ * ```
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * // Wrapping another error as cause
118
+ * try {
119
+ * await database.query('SELECT * FROM users');
120
+ * } catch (dbError) {
121
+ * throw new BaseException('Database query failed', {
122
+ * code: 'DB_ERROR',
123
+ * cause: dbError // Original error preserved
124
+ * });
125
+ * }
126
+ * ```
127
+ */
128
+ constructor(message: string, options?: BaseExceptionOptions<TDetails, TCause>);
129
+ /**
130
+ * Converts the exception to a plain JSON-serializable object.
131
+ *
132
+ * This method creates a structured representation of the exception that can be safely
133
+ * sent over HTTP, stored in databases, or logged to external systems. The output includes
134
+ * all relevant exception metadata while avoiding circular references and non-serializable values.
135
+ *
136
+ * @param options - Optional configuration to control what gets included in the serialization
137
+ * @param options.stack - Whether to include the stack trace. Defaults to true in non-production, false in production
138
+ * @param options.cause - Whether to include the error cause chain. Defaults to true
139
+ * @param options.maxCauseDepth - Maximum depth for serializing nested causes. Defaults to 10
140
+ * @returns A plain object suitable for JSON.stringify() or API responses
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * // Basic serialization
145
+ * const error = new BaseException('User not found', {
146
+ * code: 'USER_NOT_FOUND',
147
+ * statusCode: 404
148
+ * });
149
+ *
150
+ * console.log(error.toJSON());
151
+ * // {
152
+ * // __isBaseException: true,
153
+ * // __baseExceptionName: 'BaseException',
154
+ * // name: 'BaseException',
155
+ * // message: 'User not found',
156
+ * // code: 'USER_NOT_FOUND',
157
+ * // statusCode: 404,
158
+ * // timestamp: '2024-01-15T10:30:00.000Z',
159
+ * // success: false
160
+ * // }
161
+ * ```
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // Serialize with stack trace for debugging
166
+ * const error = new BaseException('Database connection failed');
167
+ * const serialized = error.toJSON({ stack: true });
168
+ *
169
+ * console.log(serialized.stack); // Full stack trace included
170
+ * ```
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * // API response usage
175
+ * app.use((err, req, res, next) => {
176
+ * if (BaseException.is(err)) {
177
+ * res.status(err.statusCode || 500).json(err.toJSON({
178
+ * stack: process.env.NODE_ENV !== 'production',
179
+ * cause: true
180
+ * }));
181
+ * }
182
+ * });
183
+ * ```
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * // Serializing error chains
188
+ * const dbError = new Error('Connection timeout');
189
+ * const appError = new BaseException('Failed to fetch user', {
190
+ * cause: dbError
191
+ * });
192
+ *
193
+ * const json = appError.toJSON({ cause: true });
194
+ * console.log(json.cause);
195
+ * // {
196
+ * // name: 'Error',
197
+ * // message: 'Connection timeout'
198
+ * // }
199
+ * ```
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * // With custom details
204
+ * interface PaymentDetails {
205
+ * transactionId: string;
206
+ * amount: number;
207
+ * }
208
+ *
209
+ * const error = new BaseException<PaymentDetails>('Payment failed', {
210
+ * code: 'PAYMENT_FAILED',
211
+ * details: {
212
+ * transactionId: 'tx_12345',
213
+ * amount: 99.99
214
+ * }
215
+ * });
216
+ *
217
+ * console.log(error.toJSON().details);
218
+ * // { transactionId: 'tx_12345', amount: 99.99 }
219
+ * ```
220
+ *
221
+ * @remarks
222
+ * - Always includes `__isBaseException: true` marker for runtime type checking
223
+ * - Timestamps are converted to ISO 8601 strings
224
+ * - Circular references in causes are prevented via maxCauseDepth
225
+ * - Stack traces are excluded by default in production for security
226
+ * - Compatible with JSON.stringify() and structured clone algorithms
227
+ */
228
+ toJSON(options?: SerializationOptions): Record<string, unknown>;
229
+ /**
230
+ * Helper to Recursively serialize causes.
231
+ */
232
+ private serializeCause;
233
+ /**
234
+ * Creates a human-readable string representation of the exception.
235
+ *
236
+ * Returns a formatted string in the format: `Name [CODE]: Message` or `Name: Message` if no code is set.
237
+ * This is automatically called when the exception is converted to a string (e.g., when logged).
238
+ *
239
+ * @returns Formatted string representation
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * // Without code
244
+ * const error = new BaseException('User not found');
245
+ * console.log(error.toString());
246
+ * // "BaseException: User not found"
247
+ * ```
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * // With error code
252
+ * const error = new BaseException('Invalid credentials', {
253
+ * code: 'AUTH_FAILED'
254
+ * });
255
+ * console.log(error.toString());
256
+ * // "BaseException [AUTH_FAILED]: Invalid credentials"
257
+ * ```
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * // String coercion
262
+ * const error = new BaseException('Payment failed', { code: 'PAYMENT_ERROR' });
263
+ * console.log('Error: ' + error);
264
+ * // "Error: BaseException [PAYMENT_ERROR]: Payment failed"
265
+ * ```
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * // In subclasses
270
+ * class ApiException extends BaseException {
271
+ * // toString automatically uses the subclass name
272
+ * }
273
+ *
274
+ * const error = new ApiException('Request failed', { code: 'API_ERROR' });
275
+ * console.log(error.toString());
276
+ * // "ApiException [API_ERROR]: Request failed"
277
+ * ```
278
+ */
279
+ toString(): string;
280
+ /**
281
+ * Custom instanceof check. When consumers import `BaseException` from built packages or
282
+ * across module boundaries, class identity can differ. Using Symbol.hasInstance
283
+ * allows `instanceof BaseException` to succeed if the object has the required BaseException API
284
+ * shape (duck typing). This preserves `instanceof` checks externally while
285
+ * keeping the current exported API intact.
286
+ */
287
+ /**
288
+ * Type guard to check if a value is a BaseException instance or has BaseException structure.
289
+ *
290
+ * This method performs both runtime `instanceof` checks and duck-typing validation.
291
+ * It works correctly even with:
292
+ * - Serialized/deserialized exceptions from JSON
293
+ * - Exceptions from different module boundaries
294
+ * - Objects that match the BaseException shape
295
+ *
296
+ * @template TException - The specific exception type to check for (defaults to BaseException)
297
+ * @param value - The value to check
298
+ * @returns True if the value is a BaseException or matches its structure
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * // Check exception instances
303
+ * const error = new BaseException('Error');
304
+ * if (BaseException.is(error)) {
305
+ * console.log(error.code); // TypeScript knows it's a BaseException
306
+ * }
307
+ * ```
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * // Works with serialized exceptions
312
+ * const error = new BaseException('Test', { code: 'TEST' });
313
+ * const json = JSON.stringify(error);
314
+ * const parsed = JSON.parse(json);
315
+ *
316
+ * BaseException.is(parsed); // true! (duck-typing)
317
+ * ```
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // Type narrowing in error handlers
322
+ * function handleError(error: unknown) {
323
+ * if (BaseException.is(error)) {
324
+ * // TypeScript narrows type to BaseException
325
+ * console.log('Code:', error.code);
326
+ * console.log('Status:', error.statusCode);
327
+ * console.log('Details:', error.details);
328
+ * } else {
329
+ * console.log('Unknown error:', error);
330
+ * }
331
+ * }
332
+ * ```
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * // Filter exceptions from array
337
+ * const errors: unknown[] = [
338
+ * new BaseException('Error 1'),
339
+ * new Error('Error 2'),
340
+ * 'string error',
341
+ * { message: 'plain object' }
342
+ * ];
343
+ *
344
+ * const baseExceptions = errors.filter(BaseException.is);
345
+ * // baseExceptions is typed as BaseException[]
346
+ * ```
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * // Check for specific subclass
351
+ * class ApiException extends BaseException {}
352
+ *
353
+ * const error: unknown = new ApiException('API failed');
354
+ * if (BaseException.is<ApiException>(error)) {
355
+ * // error is narrowed to ApiException
356
+ * }
357
+ * ```
358
+ *
359
+ * @remarks
360
+ * The duck-typing check verifies:
361
+ * - `__isBaseException === true`
362
+ * - `message` is a string
363
+ * - `success === false`
364
+ * - `__baseExceptionName === 'BaseException'`
365
+ *
366
+ * This ensures compatibility across module boundaries and serialization.
367
+ */
368
+ static is<TException extends BaseException>(value: unknown): value is TException;
369
+ /**
370
+ * Protected factory method to create a new exception instance.
371
+ *
372
+ * **Purpose**: Low-level factory that creates a NEW exception instance from a message and options.
373
+ * This is the final step in the exception creation chain used by `createFromError()`.
374
+ *
375
+ * **Key Differences from Related Methods**:
376
+ * - **`create(message, options)`** ← YOU ARE HERE
377
+ * - Creates NEW instance from known message + options
378
+ * - Simple: `new this(message, options)`
379
+ * - Override for: post-construction logic (DI, logging, tracking)
380
+ *
381
+ * - **`createFromError(error, options)`** ← Higher-level
382
+ * - Converts UNKNOWN error → extracts data → calls `create()`
383
+ * - Complex: error detection, parsing, then calls create()
384
+ * - Override for: domain-specific error detection (DB codes, HTTP status)
385
+ *
386
+ * - **`withOptions(exception, options)`** ← Different purpose
387
+ * - Mutates EXISTING instance (no new instance created)
388
+ * - Updates properties on already-created exception
389
+ * - Use for: adding metadata to existing exceptions
390
+ *
391
+ * **Flow**: `from(error)` → `createFromError(error)` → `parseErrorMessage()` + `parseErrorDetails()` → **`create(message, options)`** → `new this()`
392
+ *
393
+ * @template TDetails - Type of the details object
394
+ * @template TException - The exception type being created
395
+ * @param this - The exception constructor (auto-bound)
396
+ * @param message - The error message (already extracted/formatted)
397
+ * @param options - Optional exception metadata (code, statusCode, details, etc.)
398
+ * @returns A new exception instance of the calling class type
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * // Internal usage (this is what from() does internally)
403
+ * class ApiException extends BaseException {
404
+ * // The create method is inherited and works polymorphically
405
+ * }
406
+ *
407
+ * // When ApiException.from() calls create internally:
408
+ * const instance = ApiException.create('Error', { code: 'API_ERROR' });
409
+ * // instance is typed as ApiException, not BaseException
410
+ * ```
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * // Override for custom initialization
415
+ * class TrackedEvent {
416
+ * constructor(public eventId: string) {}
417
+ * }
418
+ *
419
+ * class TrackedExceptio extends BaseException {
420
+ * public tracker?: TrackedEvent;
421
+ *
422
+ * protected static override create<
423
+ * TDetails = unknown,
424
+ * T extends BaseException<TDetails> = BaseException<TDetails>
425
+ * >(
426
+ * this: BaseExceptionConstructor<TDetails, T>,
427
+ * message: string,
428
+ * options?: BaseExceptionOptions<TDetails>
429
+ * ): T {
430
+ * const instance = new this(message, options);
431
+ *
432
+ * // Custom post-creation logic
433
+ * if (instance instanceof TrackedExceptio) {
434
+ * instance.tracker = new TrackedEvent(generateId());
435
+ * }
436
+ *
437
+ * return instance;
438
+ * }
439
+ * }
440
+ * ```
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * // Dependency injection pattern
445
+ * class ServiceException extends BaseException {
446
+ * private static logger?: Logger;
447
+ *
448
+ * static setLogger(logger: Logger) {
449
+ * this.logger = logger;
450
+ * }
451
+ *
452
+ * protected static override create<
453
+ * TDetails = unknown,
454
+ * T extends BaseException<TDetails> = BaseException<TDetails>
455
+ * >(
456
+ * this: BaseExceptionConstructor<TDetails, T>,
457
+ * message: string,
458
+ * options?: BaseExceptionOptions<TDetails>
459
+ * ): T {
460
+ * const instance = new this(message, options);
461
+ *
462
+ * // Auto-log on creation
463
+ * if (this.logger) {
464
+ * this.logger.error(`[${instance.code}] ${message}`);
465
+ * }
466
+ *
467
+ * return instance;
468
+ * }
469
+ * }
470
+ * ```
471
+ *
472
+ * @remarks
473
+ * **When to override `create()`**:
474
+ * - Post-construction initialization (add tracking IDs, timestamps)
475
+ * - Dependency injection (inject logger, metrics service)
476
+ * - Automatic logging/monitoring on every exception creation
477
+ * - Set default properties based on application context
478
+ *
479
+ * **When NOT to override `create()` (use other methods)**:
480
+ * - Error detection/parsing → use `createFromError()` instead
481
+ * - Message extraction → use `parseErrorMessage()` instead
482
+ * - Detail extraction → use `parseErrorDetails()` instead
483
+ * - Updating existing exceptions → use `withOptions()` instead
484
+ *
485
+ * **Default implementation**: `new this(message, options)` - Creates instance with polymorphic type.
486
+ *
487
+ * **Method Comparison**:
488
+ * ```typescript
489
+ * // create: NEW instance from message
490
+ * const ex1 = create('Error', { code: 'ERR' }); // NEW BaseException
491
+ *
492
+ * // createFromError: EXTRACT data from error, then create
493
+ * const ex2 = createFromError(dbError, {}); // Extracts → create()
494
+ *
495
+ * // withOptions: MUTATE existing instance
496
+ * const ex3 = withOptions(ex1, { code: 'NEW' }); // Same instance, code changed
497
+ * console.log(ex3 === ex1); // true (mutated, not new)
498
+ * ```
499
+ *
500
+ * @see {@link createFromError} - Use this for error conversion logic
501
+ * @see {@link withOptions} - Use this to update existing exceptions
502
+ * @see {@link from} - Public method that orchestrates the creation flow
503
+ */
504
+ protected static create<TDetails = unknown, TException extends BaseException<TDetails> = BaseException<TDetails>, TCause = unknown>(this: BaseExceptionConstructor<TDetails, TException>, message: string, options?: BaseExceptionOptions<TDetails, TCause>): TException;
505
+ /**
506
+ * Creates a BaseException instance from any unknown error value.
507
+ *
508
+ * This is the primary factory method for creating exceptions from various error sources.
509
+ * It intelligently handles different input types:
510
+ * - Error objects (preserves message, stack, cause)
511
+ * - JSON strings (automatically parses)
512
+ * - Plain objects (extracts message and details)
513
+ * - Strings (uses as message)
514
+ * - Existing BaseException instances (merges options)
515
+ *
516
+ * **Extensibility**: Subclasses can override `parseErrorMessage`, `parseErrorDetails`, or
517
+ * `createFromError` to customize how errors are converted.
518
+ *
519
+ * @template TDetails - Type of the details object
520
+ * @template TException - The exception type being created (automatically inferred from the class)
521
+ * @param this - The exception constructor (automatically bound when called as `ClassName.from()`)
522
+ * @param error - The error value to convert (any type)
523
+ * @param options - Optional metadata to merge/override
524
+ * @returns A new exception instance of the calling class type
525
+ *
526
+ * @example
527
+ * ```typescript
528
+ * // Convert standard Error
529
+ * try {
530
+ * JSON.parse('invalid');
531
+ * } catch (err) {
532
+ * throw BaseException.from(err);
533
+ * // BaseException with err.message, err.stack preserved
534
+ * }
535
+ * ```
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * // From string
540
+ * const error = BaseException.from('Something went wrong');
541
+ * console.log(error.message); // \"Something went wrong\"
542
+ * ```
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * // From plain object (API error responses)\n * const apiError = {\n * message: 'Validation failed',\n * code: 'VALIDATION_ERROR',\n * statusCode: 400,\n * errors: [{ field: 'email', message: 'Invalid format' }]\n * };\n *\n * const exception = BaseException.from(apiError);\n * console.log(exception.code); // 'VALIDATION_ERROR'\n * console.log(exception.statusCode); // 400\n * console.log(exception.details); // Contains the API error details\n * ```\n *\n * @example\n * ```typescript\n * // From JSON string\n * const jsonError = '{\"message\":\"Error\",\"code\":\"ERR_001\"}';\n * const error = BaseException.from(jsonError);\n * // Automatically parsed and converted\n * ```\n *\n * @example\n * ```typescript\n * // With additional options\n * const dbError = new Error('Connection timeout');\n * const appError = BaseException.from(dbError, {\n * code: 'DB_CONNECTION_ERROR',\n * statusCode: 503,\n * details: { database: 'users_db', timeout: 5000 }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Subclass usage - maintains type\n * class ApiException extends BaseException<{ endpoint: string }> {\n * protected static override parseErrorDetails(error: unknown) {\n * const details = super.parseErrorDetails(error);\n * return {\n * ...details,\n * endpoint: typeof error === 'object' && error !== null \n * ? (error as any).url || (error as any).endpoint\n * : undefined\n * };\n * }\n * }\n *\n * const apiError = { message: 'Failed', url: '/api/users' };\n * const exception = ApiException.from(apiError);\n * // exception is typed as ApiException\n * console.log(exception.details?.endpoint); // '/api/users'\n * ```\n *\n * @example\n * ```typescript\n * // Error chain preservation\n * const rootCause = new Error('Network timeout');\n * const wrapped = BaseException.from(rootCause, {\n * code: 'NETWORK_ERROR'\n * });\n *\n * console.log(wrapped.cause === rootCause); // true\n * console.log(wrapped.toJSON().cause); // Serialized root cause\n * ```\n *\n * @example\n * ```typescript\n * // Re-using existing exception with new options\n * const original = new BaseException('Error');\n * const updated = BaseException.from(original, {\n * code: 'UPDATED_CODE'\n * });\n *\n * console.log(updated === original); // true (same instance)\n * console.log(updated.code); // 'UPDATED_CODE' (merged)\n * ```\n *\n * @remarks\n * **Behavior**:\n * - If `error` is already an instance of the calling class, returns it with options merged\n * - Automatically detects and parses JSON strings\n * - Extracts `code` and `statusCode` from error objects when available\n * - Preserves error chains via the `cause` property\n * - Sets `statusCode` to 500 by default for unknown errors\n * - Override `parseErrorMessage()` to customize message extraction\n * - Override `parseErrorDetails()` to extract custom fields\n * - Override `createFromError()` for complete control over error conversion\n * - Override `from()` itself for pre/post-processing logic\n *\n * @see {@link parseErrorMessage} - Customize message extraction\n * @see {@link parseErrorDetails} - Customize details extraction\n * @see {@link createFromError} - Full control over error conversion\n * @see {@link withOptions} - Merge options into existing exceptions\n */
547
+ static from<TDetails = unknown, TException extends BaseException<TDetails> = BaseException<TDetails>, TCause = unknown>(this: BaseExceptionConstructor<TDetails, TException>, error: unknown, options?: BaseExceptionOptions<TDetails, TCause>): TException;
548
+ /**
549
+ * Protected method to create an exception from an error source.
550
+ *
551
+ * This is the **core conversion method** called by `from()` after it has handled
552
+ * JSON parsing and existing instance checks. Override this method in subclasses
553
+ * to implement custom error handling logic, such as detecting specific error types
554
+ * and setting appropriate codes, status codes, and details.
555
+ *
556
+ * The default implementation:
557
+ * 1. Calls `parseErrorMessage(error, options)` to extract the message
558
+ * 2. Calls `parseErrorDetails(error, options)` to extract structured details
559
+ * 3. Determines the final `code` from options, details.code, or details.errorCode
560
+ * 4. Determines the final `statusCode` from options, details.statusCode, or defaults to 500
561
+ * 5. Creates a new exception instance via `create()` with all extracted data
562
+ * 6. Sets the original error as the `cause`
563
+ *
564
+ * **Protected**: This is an extensibility hook. Override this when you need complete
565
+ * control over how errors are converted to exceptions for domain-specific logic.
566
+ *
567
+ * @template TDetails - Type of the details object
568
+ * @template TException - The exception type being created
569
+ * @param this - The exception constructor (auto-bound)
570
+ * @param error - The error value to convert (any type)
571
+ * @param options - Optional metadata to merge/override
572
+ * @returns A new exception instance with extracted data
573
+ *
574
+ * @example
575
+ * ```typescript
576
+ * // Database exception with error code detection
577
+ * interface DbErrorDetails {
578
+ * query?: string;
579
+ * table?: string;
580
+ * constraint?: string;
581
+ * }
582
+ *
583
+ * class DatabaseException extends BaseException<DbErrorDetails> {
584
+ * protected static override createFromError<
585
+ * TDetails = DbErrorDetails,
586
+ * T extends BaseException<TDetails> = BaseException<TDetails>
587
+ * >(
588
+ * this: BaseExceptionConstructor<TDetails, T>,
589
+ * error: unknown,
590
+ * options?: BaseExceptionOptions<TDetails>
591
+ * ): T {
592
+ * if (typeof error === 'object' && error !== null) {
593
+ * const err = error as Record<string, unknown>;
594
+ *
595
+ * // PostgreSQL duplicate key error
596
+ * if (err.code === '23505') {
597
+ * return new this('Duplicate entry detected', {
598
+ * ...options,
599
+ * code: 'DB_DUPLICATE_ENTRY',
600
+ * statusCode: 409,
601
+ * details: {
602
+ * ...options?.details,
603
+ * table: err.table as string,
604
+ * constraint: err.constraint as string,
605
+ * query: err.query as string
606
+ * } as TDetails,
607
+ * cause: error
608
+ * });
609
+ * }
610
+ *
611
+ * // Foreign key constraint error
612
+ * if (err.code === '23503') {
613
+ * return new this('Foreign key violation', {
614
+ * ...options,
615
+ * code: 'DB_FK_VIOLATION',
616
+ * statusCode: 400,
617
+ * details: {
618
+ * ...options?.details,
619
+ * table: err.table as string,
620
+ * constraint: err.constraint as string
621
+ * } as TDetails,
622
+ * cause: error
623
+ * });
624
+ * }
625
+ * }
626
+ *
627
+ * // Fall back to default behavior for unknown errors
628
+ * return super.createFromError(error, options) as T;
629
+ * }
630
+ * }
631
+ * ```
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * // HTTP exception with status code mapping
636
+ * interface HttpErrorDetails {
637
+ * statusCode: number;
638
+ * path?: string;
639
+ * method?: string;
640
+ * headers?: Record<string, string>;
641
+ * }
642
+ *
643
+ * class HttpException extends BaseException<HttpErrorDetails> {
644
+ * protected static override createFromError<
645
+ * TDetails = HttpErrorDetails,
646
+ * T extends BaseException<TDetails> = BaseException<TDetails>
647
+ * >(
648
+ * this: BaseExceptionConstructor<TDetails, T>,
649
+ * error: unknown,
650
+ * options?: BaseExceptionOptions<TDetails>
651
+ * ): T {
652
+ * if (typeof error === 'object' && error !== null) {
653
+ * const err = error as Record<string, unknown>;
654
+ * const status = typeof err.statusCode === 'number' ? err.statusCode : 500;
655
+ *
656
+ * // Map status codes to standard messages
657
+ * const statusMessages: Record<number, string> = {
658
+ * 400: 'Bad Request',
659
+ * 401: 'Unauthorized',
660
+ * 403: 'Forbidden',
661
+ * 404: 'Not Found',
662
+ * 500: 'Internal Server Error',
663
+ * 502: 'Bad Gateway',
664
+ * 503: 'Service Unavailable'
665
+ * };
666
+ *
667
+ * const message = typeof err.message === 'string'
668
+ * ? err.message
669
+ * : statusMessages[status] || 'HTTP Error';
670
+ *
671
+ * return new this(message, {
672
+ * ...options,
673
+ * code: `HTTP_${status}`,
674
+ * statusCode: status,
675
+ * details: {
676
+ * ...options?.details,
677
+ * statusCode: status,
678
+ * path: typeof err.path === 'string' ? err.path : undefined,
679
+ * method: typeof err.method === 'string' ? err.method : undefined,
680
+ * headers: err.headers as Record<string, string>
681
+ * } as TDetails,
682
+ * cause: error
683
+ * });
684
+ * }
685
+ *
686
+ * return super.createFromError(error, options) as T;
687
+ * }
688
+ * }
689
+ * ```
690
+ *
691
+ * @example
692
+ * ```typescript
693
+ * // Validation exception with error aggregation
694
+ * interface ValidationErrorDetails {
695
+ * errors: Array<{ field: string; message: string; rule: string }>;
696
+ * }
697
+ *
698
+ * class ValidationException extends BaseException<ValidationErrorDetails> {
699
+ * protected static override createFromError<
700
+ * TDetails = ValidationErrorDetails,
701
+ * T extends BaseException<TDetails> = BaseException<TDetails>
702
+ * >(
703
+ * this: BaseExceptionConstructor<TDetails, T>,
704
+ * error: unknown,
705
+ * options?: BaseExceptionOptions<TDetails>
706
+ * ): T {
707
+ * if (typeof error === 'object' && error !== null) {
708
+ * const err = error as Record<string, unknown>;
709
+ *
710
+ * // Check for validation error array (Joi, Yup, etc.)
711
+ * if (Array.isArray(err.details) && err.details.length > 0) {
712
+ * const validationErrors = err.details.map((detail: any) => ({
713
+ * field: detail.path?.join('.') || 'unknown',
714
+ * message: detail.message || 'Validation failed',
715
+ * rule: detail.type || 'unknown'
716
+ * }));
717
+ *
718
+ * const firstError = validationErrors[0];
719
+ * const message = validationErrors.length === 1
720
+ * ? `Validation failed for ${firstError.field}: ${firstError.message}`
721
+ * : `Validation failed for ${validationErrors.length} fields`;
722
+ *
723
+ * return new this(message, {
724
+ * ...options,
725
+ * code: 'VALIDATION_ERROR',
726
+ * statusCode: 400,
727
+ * details: {
728
+ * ...options?.details,
729
+ * errors: validationErrors
730
+ * } as TDetails,
731
+ * cause: error
732
+ * });
733
+ * }
734
+ * }
735
+ *
736
+ * return super.createFromError(error, options) as T;
737
+ * }
738
+ * }
739
+ * ```
740
+ *
741
+ * @remarks
742
+ * **When to override `createFromError()`**:
743
+ * - Domain-specific error detection (e.g., database error codes, HTTP status)
744
+ * - Map error types to standardized codes/messages
745
+ * - Aggregate or transform complex error structures
746
+ * - Complete control over error-to-exception conversion
747
+ *
748
+ * **When NOT to override `createFromError()` (use other methods)**:
749
+ * - Only custom message formatting → use `parseErrorMessage()` instead
750
+ * - Only custom detail extraction → use `parseErrorDetails()` instead
751
+ * - Simple pre/post processing → override `from()` itself
752
+ * - Post-construction logic → use `create()` instead
753
+ *
754
+ * **Key Differences from Related Methods**:
755
+ * - **`createFromError(error, options)`** ← YOU ARE HERE
756
+ * - Converts UNKNOWN error → extracts data → creates NEW instance
757
+ * - Complex: detects error type, parses fields, calls `create()`
758
+ * - Override for: error detection (DB codes, HTTP status, validation)
759
+ *
760
+ * - **`create(message, options)`** ← Lower-level
761
+ * - Creates NEW instance from KNOWN message + options
762
+ * - Simple: `new this(message, options)`
763
+ * - Override for: post-construction logic only
764
+ *
765
+ * - **`withOptions(exception, options)`** ← Different purpose
766
+ * - Mutates EXISTING instance (no creation)
767
+ * - Just updates properties
768
+ * - Use for: modifying already-created exceptions
769
+ *
770
+ * **Flow in action**:
771
+ * ```typescript
772
+ * // User calls:
773
+ * BaseException.from(unknownError)
774
+ * ↓
775
+ * // Internally:
776
+ * createFromError(unknownError) // ← YOU ARE HERE (detect & extract)
777
+ * ↓
778
+ * parseErrorMessage(unknownError) // Extract message
779
+ * parseErrorDetails(unknownError) // Extract details
780
+ * ↓
781
+ * create(message, options) // Create new instance
782
+ * ↓
783
+ * new this(message, options) // Constructor
784
+ * ```
785
+ *
786
+ * **Implementation tips**:
787
+ * - Always fall back to `super.createFromError()` for unknown error types
788
+ * - Preserve the original error as `cause` for debugging
789
+ * - Use type guards to safely check error properties
790
+ * - Return specific status codes (400 for client errors, 500 for server errors)
791
+ * - Include relevant context in details for troubleshooting
792
+ *
793
+ * **Method Comparison Example**:
794
+ * ```typescript
795
+ * const dbError = { code: '23505', message: 'Duplicate key' };
796
+ *
797
+ * // createFromError: DETECTS + EXTRACTS + CREATES
798
+ * const ex1 = createFromError(dbError, {});
799
+ * // Detects code 23505 → sets code='DB_DUPLICATE', status=409, etc.
800
+ *
801
+ * // create: Just CREATES (you provide everything)
802
+ * const ex2 = create('Duplicate key', { code: 'DB_DUPLICATE', statusCode: 409 });
803
+ * // No detection, you already know what to set
804
+ *
805
+ * // withOptions: MUTATES existing
806
+ * const ex3 = withOptions(ex2, { code: 'NEW_CODE' });
807
+ * // ex3 === ex2 (same instance, just code changed)
808
+ * ```
809
+ *
810
+ * @see {@link from} - Public method that calls this after handling JSON/instances
811
+ * @see {@link create} - Lower-level method for creating instances
812
+ * @see {@link parseErrorMessage} - Override for custom message extraction only
813
+ * @see {@link parseErrorDetails} - Override for custom detail extraction only
814
+ */
815
+ protected static createFromError<TDetails = unknown, TException extends BaseException<TDetails> = BaseException<TDetails>, TCause = unknown>(this: BaseExceptionConstructor<TDetails, TException>, error: unknown, options?: BaseExceptionOptions<TDetails, TCause>): TException;
816
+ static isValidationError(error: unknown): error is ValidatorError;
817
+ static getValidationError(error: unknown): ValidatorError | ValidatorClassError | ValidatorBulkError | null;
818
+ /**
819
+ * Protected method to extract a human-readable message from an error value.
820
+ *
821
+ * Override this in subclasses to customize how messages are extracted from different
822
+ * error types. For example, you might want to format validation errors differently
823
+ * or add context from error properties.
824
+ *
825
+ * The default implementation:
826
+ * 1. Extracts `message` property from objects
827
+ * 2. Uses the string value directly if error is a string
828
+ * 3. Falls back to `Error.message` for Error instances
829
+ * 4. Uses `options.fallbackMessage` if provided
830
+ * 5. Defaults to "Unknown Error" if nothing else works
831
+ *
832
+ * @param error - The error value to extract a message from
833
+ * @param options - Optional configuration with fallbackMessage
834
+ * @returns The extracted error message string
835
+ *
836
+ * @example
837
+ * ```typescript
838
+ * // Custom validation exception with formatted message
839
+ * class ValidationException extends BaseException {
840
+ * protected static override parseErrorMessage(
841
+ * error: unknown,
842
+ * options?: BaseExceptionOptions
843
+ * ): string {
844
+ * if (typeof error === 'object' && error !== null) {
845
+ * const err = error as Record<string, unknown>;
846
+ *
847
+ * // Check for validation error array
848
+ * if (Array.isArray(err.errors) && err.errors.length > 0) {
849
+ * const firstError = err.errors[0];
850
+ * if (typeof firstError === 'object' && firstError !== null) {
851
+ * const field = (firstError as any).field;
852
+ * const msg = (firstError as any).message;
853
+ * return `Validation failed for ${field}: ${msg}`;
854
+ * }
855
+ * }
856
+ * }
857
+ *
858
+ * // Fall back to default
859
+ * return super.parseErrorMessage(error, options);
860
+ * }
861
+ * }
862
+ *
863
+ * const validationError = {
864
+ * errors: [{ field: 'email', message: 'Invalid format' }]
865
+ * };
866
+ * const ex = ValidationException.from(validationError);
867
+ * // ex.message === 'Validation failed for email: Invalid format'
868
+ * ```
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // Add table context to database errors
873
+ * class DatabaseException extends BaseException {
874
+ * protected static override parseErrorMessage(
875
+ * error: unknown,
876
+ * options?: BaseExceptionOptions
877
+ * ): string {
878
+ * const baseMessage = super.parseErrorMessage(error, options);
879
+ *
880
+ * if (typeof error === 'object' && error !== null) {
881
+ * const err = error as Record<string, unknown>;
882
+ * if (typeof err.table === 'string') {
883
+ * return `${baseMessage} (table: ${err.table})`;
884
+ * }
885
+ * }
886
+ *
887
+ * return baseMessage;
888
+ * }
889
+ * }
890
+ * ```
891
+ *
892
+ * @remarks
893
+ * - Always returns a non-empty string (minimum "Unknown Error")
894
+ * - Sanitizes "undefined" strings to "Unknown Error"
895
+ * - Called by `createFromError()` during error conversion
896
+ */
897
+ protected static parseErrorMessage(error: unknown, options?: BaseExceptionOptions): string;
898
+ /**
899
+ * Protected method to extract structured details from an error value.
900
+ *
901
+ * Override this in subclasses to extract domain-specific fields from errors.
902
+ * For example, API errors might have `endpoint` and `method`, while database
903
+ * errors might have `query` and `table`.
904
+ *
905
+ * The default implementation:
906
+ * 1. Spreads all properties from the error object (if it's an object)
907
+ * 2. Merges with `options.details` (options take precedence)
908
+ *
909
+ * @template TDetails - Type of the details object
910
+ * @template TCause - Type of the cause object
911
+ * @param error - The error value to extract details from
912
+ * @param options - Optional configuration with details to merge
913
+ * @returns Object containing extracted details
914
+ *
915
+ * @example
916
+ * ```typescript
917
+ * // Extract API-specific fields
918
+ * interface ApiErrorDetails {
919
+ * endpoint?: string;
920
+ * method?: string;
921
+ * requestId?: string;
922
+ * }
923
+ *
924
+ * class ApiException extends BaseException<ApiErrorDetails> {
925
+ * protected static override parseErrorDetails<
926
+ * TDetails = ApiErrorDetails
927
+ * >(
928
+ * error: unknown,
929
+ * options?: BaseExceptionOptions<TDetails>
930
+ * ) {
931
+ * const baseDetails = super.parseErrorDetails(error, options);
932
+ *
933
+ * if (typeof error === 'object' && error !== null) {
934
+ * const err = error as Record<string, unknown>;
935
+ * return {
936
+ * ...baseDetails,
937
+ * endpoint: typeof err.url === 'string' ? err.url : undefined,
938
+ * method: typeof err.method === 'string' ? err.method : undefined,
939
+ * requestId: typeof err.requestId === 'string' ? err.requestId : undefined
940
+ * };
941
+ * }
942
+ *
943
+ * return baseDetails;
944
+ * }
945
+ * }
946
+ * ```
947
+ *
948
+ * @example
949
+ * ```typescript
950
+ * // Extract database-specific fields
951
+ * interface DbErrorDetails {
952
+ * query?: string;
953
+ * table?: string;
954
+ * constraint?: string;
955
+ * }
956
+ *
957
+ * class DatabaseException extends BaseException<DbErrorDetails> {
958
+ * protected static override parseErrorDetails<
959
+ * TDetails = DbErrorDetails
960
+ * >(
961
+ * error: unknown,
962
+ * options?: BaseExceptionOptions<TDetails>
963
+ * ) {
964
+ * const baseDetails = super.parseErrorDetails(error, options);
965
+ *
966
+ * if (typeof error === 'object' && error !== null) {
967
+ * const err = error as Record<string, unknown>;
968
+ * return {
969
+ * ...baseDetails,
970
+ * query: typeof err.sql === 'string' ? err.sql : undefined,
971
+ * table: typeof err.table === 'string' ? err.table : undefined,
972
+ * constraint: typeof err.constraint === 'string' ? err.constraint : undefined
973
+ * };
974
+ * }
975
+ *
976
+ * return baseDetails;
977
+ * }
978
+ * }
979
+ * ```
980
+ *
981
+ * @remarks
982
+ * - Returns all properties from error objects by default
983
+ * - Options.details take precedence over extracted details
984
+ * - Called by `createFromError()` during error conversion
985
+ * - Use TypeScript interfaces to ensure type safety of extracted fields
986
+ */
987
+ protected static parseErrorDetails<TDetails = unknown, TCause = unknown>(error: unknown, options?: BaseExceptionOptions<TDetails, TCause>): TDetails;
988
+ /**
989
+ * Merges additional options into an existing BaseException instance.
990
+ *
991
+ * This utility method mutates the exception instance by updating its `code`,
992
+ * `statusCode`, and `details` properties with values from the options object.
993
+ * Used internally by `from()` when reusing existing exceptions.
994
+ *
995
+ * @template TException - The exception type (automatically inferred)
996
+ * @param error - The exception instance to update
997
+ * @param options - Options to merge into the exception
998
+ * @returns The same exception instance with updated properties
999
+ *
1000
+ * @example
1001
+ * ```typescript
1002
+ * // Update error code
1003
+ * const error = new BaseException('Failed');
1004
+ * BaseException.withOptions(error, { code: 'NEW_CODE' });
1005
+ * console.log(error.code); // 'NEW_CODE'
1006
+ * ```
1007
+ *
1008
+ * @example
1009
+ * ```typescript
1010
+ * // Merge details
1011
+ * const error = new BaseException('Error', {
1012
+ * details: { originalData: 'value1' }
1013
+ * });
1014
+ *
1015
+ * BaseException.withOptions(error, {
1016
+ * details: { additionalData: 'value2' }
1017
+ * });
1018
+ *
1019
+ * console.log(error.details);
1020
+ * // { originalData: 'value1', additionalData: 'value2' }
1021
+ * ```
1022
+ *
1023
+ * @example
1024
+ * ```typescript
1025
+ * // Update multiple properties
1026
+ * const error = new BaseException('Error');
1027
+ * BaseException.withOptions(error, {
1028
+ * code: 'UPDATED_CODE',
1029
+ * statusCode: 503,
1030
+ * details: { retry: true }
1031
+ * });
1032
+ * ```
1033
+ *
1034
+ * @remarks
1035
+ * **IMPORTANT: This method MUTATES the input exception instance.**
1036
+ * Unlike `create()` and `createFromError()` which create NEW instances,
1037
+ * `withOptions()` modifies the existing exception in-place for performance.
1038
+ *
1039
+ * **Key Differences from Related Methods**:
1040
+ * - **`withOptions(exception, options)`** ← YOU ARE HERE
1041
+ * - **MUTATES** existing instance (no new instance created)
1042
+ * - Just updates: `error.code = ...; error.statusCode = ...`
1043
+ * - Use for: merging options into already-created exceptions
1044
+ * - Returns: THE SAME instance (not a copy)
1045
+ *
1046
+ * - **`create(message, options)`** ← Creates NEW
1047
+ * - Creates NEW instance from known message + options
1048
+ * - Simple: `new this(message, options)`
1049
+ * - Returns: NEW exception instance
1050
+ *
1051
+ * - **`createFromError(error, options)`** ← Converts + Creates NEW
1052
+ * - Converts unknown error → extracts data → creates NEW instance
1053
+ * - Complex: detects, parses, then creates
1054
+ * - Returns: NEW exception instance
1055
+ *
1056
+ * **When this is called**:
1057
+ * - Automatically by `from()` when the error is already an instance of the target class
1058
+ * - For merging additional options into an existing exception without recreating it
1059
+ *
1060
+ * **Method Comparison Example**:
1061
+ * ```typescript
1062
+ * const original = new BaseException('Error', { code: 'ORIG' });
1063
+ *
1064
+ * // withOptions: MUTATES (same instance)
1065
+ * const updated = BaseException.withOptions(original, { code: 'NEW' });
1066
+ * console.log(updated === original); // true (same instance!)
1067
+ * console.log(original.code); // 'NEW' (original was mutated)
1068
+ *
1069
+ * // create: NEW instance
1070
+ * const created = BaseException.create('Error', { code: 'CREATED' });
1071
+ * console.log(created === original); // false (different instance)
1072
+ *
1073
+ * // createFromError: NEW instance from error
1074
+ * const converted = BaseException.createFromError(someError, {});
1075
+ * console.log(converted === original); // false (different instance)
1076
+ * ```
1077
+ *
1078
+ * **Performance reasoning**:
1079
+ * Mutation is intentional to avoid unnecessary object creation when reusing
1080
+ * exceptions via `from()`. If you need a copy, create a new instance instead.
1081
+ *
1082
+ * @see {@link from} - Calls this when error is already an instance of target class
1083
+ * @see {@link create} - Creates new instances (doesn't mutate)
1084
+ * @see {@link createFromError} - Converts errors to new instances (doesn't mutate)
1085
+ */
1086
+ static withOptions<TException extends BaseException>(error: TException, options?: BaseExceptionOptions): TException;
1087
+ /**
1088
+ * Wraps an async operation, converting any thrown errors into BaseException.
1089
+ *
1090
+ * Executes the operation and returns its result if successful. If it throws,
1091
+ * converts the error to a BaseException using `from()` and **RE-THROWS** it.
1092
+ *
1093
+ * **Error Handling Pattern**: Traditional exception throwing (try/catch pattern)
1094
+ *
1095
+ * **Key Differences from Related Methods**:
1096
+ * - **`wrap(operation, options)`** ← YOU ARE HERE
1097
+ * - **ASYNC ONLY** (returns Promise)
1098
+ * - **THROWS** BaseException on error (traditional exception pattern)
1099
+ * - Use when: You want exceptions to bubble up to error handlers
1100
+ * - Pattern: try/catch or framework error handlers catch it
1101
+ *
1102
+ * - **`tryCatch(operation, options)`** ← Returns errors instead of throwing
1103
+ * - **ASYNC** (returns Promise<[error, null] | [null, result]>)
1104
+ * - **RETURNS** error tuple (Result/Either pattern, Go-style)
1105
+ * - Use when: You want explicit error handling without try/catch
1106
+ * - Pattern: `const [error, result] = await tryCatch(...)`
1107
+ *
1108
+ * - **`tryCatchSync(operation, options)`** ← Sync version of tryCatch
1109
+ * - **SYNC** (returns [error, null] | [null, result])
1110
+ * - **RETURNS** error tuple (Result/Either pattern)
1111
+ * - Use when: Synchronous operations, prefer explicit errors
1112
+ * - Pattern: `const [error, result] = tryCatchSync(...)`
1113
+ *
1114
+ * @template TResult - The return type of the operation
1115
+ * @template TDetails - Type of exception details
1116
+ * @template TCause - Type of the cause
1117
+ * @param operation - Async function to execute
1118
+ * @param options - Optional exception metadata to add if an error occurs
1119
+ * @returns Promise resolving to the operation result
1120
+ * @throws {BaseException} If the operation throws (converted from original error)
1121
+ *
1122
+ * @example
1123
+ * ```typescript
1124
+ * // Wrap API call
1125
+ * const data = await BaseException.wrap(
1126
+ * async () => fetch('/api/users').then(r => r.json()),
1127
+ * { code: 'API_ERROR', statusCode: 503 }
1128
+ * );
1129
+ * // On error, throws BaseException with code 'API_ERROR'
1130
+ * ```
1131
+ *
1132
+ * @example
1133
+ * ```typescript
1134
+ * // Wrap database query
1135
+ * async function getUser(id: number) {
1136
+ * return BaseException.wrap(
1137
+ * async () => db.query('SELECT * FROM users WHERE id = ?', [id]),
1138
+ * {
1139
+ * code: 'DB_ERROR',
1140
+ * statusCode: 500,
1141
+ * details: { query: 'getUser', userId: id }
1142
+ * }
1143
+ * );
1144
+ * }
1145
+ * ```
1146
+ *
1147
+ * @example
1148
+ * ```typescript
1149
+ * // Chain wrapping
1150
+ * const result = await BaseException.wrap(
1151
+ * async () => {
1152
+ * const user = await getUser(123);
1153
+ * const orders = await getOrders(user.id);
1154
+ * return { user, orders };
1155
+ * },
1156
+ * { code: 'USER_ORDERS_ERROR' }
1157
+ * );
1158
+ * ```
1159
+ *
1160
+ * @remarks
1161
+ * **Error Handling Pattern**: THROWS exceptions (traditional)
1162
+ * - Errors bubble up to framework error handlers or try/catch blocks
1163
+ * - Good for: Express/NestJS middleware, API routes, service layers
1164
+ * - Clean syntax when you don't need inline error handling
1165
+ *
1166
+ * **When to use `wrap()` vs `tryCatch()`**:
1167
+ * - Use `wrap()` when exceptions should bubble up (APIs, middleware)
1168
+ * - Use `tryCatch()` when you need explicit error handling inline
1169
+ *
1170
+ * **Comparison Example**:
1171
+ * ```typescript
1172
+ * // wrap: THROWS (traditional exception)
1173
+ * async function fetchUser1(id: number) {
1174
+ * return BaseException.wrap(
1175
+ * async () => db.users.findById(id),
1176
+ * { code: 'USER_FETCH_ERROR' }
1177
+ * );
1178
+ * // On error: throws BaseException, caller must try/catch
1179
+ * }
1180
+ *
1181
+ * // tryCatch: RETURNS error (Result pattern)
1182
+ * async function fetchUser2(id: number) {
1183
+ * const [error, user] = await BaseException.tryCatch(
1184
+ * async () => db.users.findById(id),
1185
+ * { code: 'USER_FETCH_ERROR' }
1186
+ * );
1187
+ * if (error) return null; // Handle inline
1188
+ * return user;
1189
+ * }
1190
+ *
1191
+ * // Usage:
1192
+ * try {
1193
+ * const user = await fetchUser1(123); // May throw
1194
+ * } catch (e) {
1195
+ * // Handle error here
1196
+ * }
1197
+ *
1198
+ * const user = await fetchUser2(123); // Never throws, returns null on error
1199
+ * ```
1200
+ *
1201
+ * @see {@link tryCatch} - Async version that returns error tuples instead of throwing
1202
+ * @see {@link tryCatchSync} - Sync version that returns error tuples
1203
+ * @see {@link from} - The conversion method used internally
1204
+ */
1205
+ static wrap<TResult, TDetails = unknown, TCause = unknown>(operation: () => Promise<TResult>, options?: BaseExceptionOptions<TDetails, TCause>): Promise<TResult>;
1206
+ /**
1207
+ * Immediately throws a BaseException created from the given error value.
1208
+ *
1209
+ * This is a convenience method equivalent to `throw BaseException.from(error, options)`.
1210
+ * Useful for concise one-liner error throwing with proper conversion.
1211
+ *
1212
+ * @template TDetails - Type of exception details
1213
+ * @template TCause - Type of exception cause
1214
+ * @param error - The error value to convert and throw
1215
+ * @param options - Optional exception metadata
1216
+ * @throws {BaseException} Always throws
1217
+ *
1218
+ * @example
1219
+ * ```typescript
1220
+ * // One-liner error throwing
1221
+ * if (!user) {
1222
+ * BaseException.throw('User not found', {
1223
+ * code: 'USER_NOT_FOUND',
1224
+ * statusCode: 404
1225
+ * });
1226
+ * }
1227
+ * ```
1228
+ *
1229
+ * @example
1230
+ * ```typescript
1231
+ * // Convert and re-throw
1232
+ * try {
1233
+ * riskyOperation();
1234
+ * } catch (err) {
1235
+ * BaseException.throw(err, {
1236
+ * code: 'OPERATION_FAILED',
1237
+ * details: { context: 'important data' }
1238
+ * });
1239
+ * }
1240
+ * ```
1241
+ *
1242
+ * @example
1243
+ * ```typescript
1244
+ * // Conditional throwing
1245
+ * const validateAge = (age: number) => {
1246
+ * age < 0 && BaseException.throw('Invalid age', { code: 'VALIDATION_ERROR' });
1247
+ * age > 150 && BaseException.throw('Age too high', { code: 'VALIDATION_ERROR' });
1248
+ * return age;
1249
+ * };
1250
+ * ```
1251
+ *
1252
+ * @remarks
1253
+ * Return type is `never` since this function always throws.
1254
+ * Helps TypeScript understand control flow correctly.
1255
+ *
1256
+ * @see {@link from} - Creates exception without throwing
1257
+ */
1258
+ static throw<TDetails = unknown, TCause = unknown>(error: unknown, options?: BaseExceptionOptions<TDetails, TCause>): never;
1259
+ /**
1260
+ * Safely executes a synchronous operation and returns a Result tuple.
1261
+ *
1262
+ * This method implements the **Result/Either pattern**, returning a tuple of
1263
+ * `[error, null]` if the operation fails, or `[null, result]` if it succeeds.
1264
+ * This eliminates the need for try-catch blocks and makes error handling explicit.
1265
+ *
1266
+ * **Error Handling Pattern**: Returns error tuples (Go/Rust-style)
1267
+ *
1268
+ * **Key Differences from Related Methods**:
1269
+ * - **`tryCatchSync(operation, options)`** ← YOU ARE HERE
1270
+ * - **SYNC** (returns immediately)
1271
+ * - **RETURNS** error tuple: `[error, null] | [null, result]`
1272
+ * - Use when: Synchronous operations, explicit error handling
1273
+ * - Pattern: `const [error, result] = tryCatchSync(...)`
1274
+ *
1275
+ * - **`tryCatch(operation, options)`** ← Async version
1276
+ * - **ASYNC** (returns Promise<[error, null] | [null, result]>)
1277
+ * - **RETURNS** error tuple (same pattern, but async)
1278
+ * - Use when: Async operations, explicit error handling
1279
+ * - Pattern: `const [error, result] = await tryCatch(...)`
1280
+ *
1281
+ * - **`wrap(operation, options)`** ← Different error pattern
1282
+ * - **ASYNC ONLY**
1283
+ * - **THROWS** BaseException on error (traditional exceptions)
1284
+ * - Use when: You want errors to bubble up to handlers
1285
+ * - Pattern: `try { await wrap(...) } catch (e) { ... }`
1286
+ *
1287
+ * **Type-safe**: When called on subclasses, returns that subclass type in the error position.
1288
+ *
1289
+ * @template TResult - The return type of the operation
1290
+ * @template TDetails - Type of exception details
1291
+ * @template TCause - Type of exception cause
1292
+ * @param this - The exception constructor (auto-bound)
1293
+ * @param operation - Synchronous function to execute
1294
+ * @param options - Optional exception metadata if an error occurs
1295
+ * @returns Tuple of `[error, null]` or `[null, result]` (never throws)
1296
+ *
1297
+ * @example
1298
+ * ```typescript
1299
+ * // Basic usage
1300
+ * const [error, result] = BaseException.tryCatchSync(() => {
1301
+ * return JSON.parse(someString);
1302
+ * });
1303
+ *
1304
+ * if (error) {
1305
+ * console.error('Parse failed:', error.message);
1306
+ * return;
1307
+ * }
1308
+ *
1309
+ * console.log('Parsed:', result);
1310
+ * ```
1311
+ *
1312
+ * @example
1313
+ * ```typescript
1314
+ * // With additional error metadata
1315
+ * const [error, user] = BaseException.tryCatchSync(
1316
+ * () => validateUser(data),
1317
+ * {
1318
+ * code: 'VALIDATION_ERROR',
1319
+ * statusCode: 400,
1320
+ * details: { input: data }
1321
+ * }
1322
+ * );
1323
+ * ```
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * // Chaining operations
1328
+ * function processData(input: string) {
1329
+ * const [parseError, data] = BaseException.tryCatchSync(() =>
1330
+ * JSON.parse(input)
1331
+ * );
1332
+ * if (parseError) return { error: parseError };
1333
+ *
1334
+ * const [validError, validated] = BaseException.tryCatchSync(() =>
1335
+ * validateData(data)
1336
+ * );
1337
+ * if (validError) return { error: validError };
1338
+ *
1339
+ * return { data: validated };
1340
+ * }
1341
+ * ```
1342
+ *
1343
+ * @example
1344
+ * ```typescript
1345
+ * // With subclass - maintains type
1346
+ * class ValidationException extends BaseException {}
1347
+ *
1348
+ * const [error, result] = ValidationException.tryCatchSync(() =>
1349
+ * validateEmail(email)
1350
+ * );
1351
+ *
1352
+ * if (error) {
1353
+ * // error is typed as ValidationException
1354
+ * console.log(error.name); // 'ValidationException'
1355
+ * }
1356
+ * ```
1357
+ *
1358
+ * @example
1359
+ * ```typescript
1360
+ * // Functional programming style
1361
+ * const results = data.map(item =>
1362
+ * BaseException.tryCatchSync(() => processItem(item))
1363
+ * );
1364
+ *
1365
+ * const errors = results
1366
+ * .filter(([err]) => err !== null)
1367
+ * .map(([err]) => err);
1368
+ *
1369
+ * const values = results
1370
+ * .filter(([, val]) => val !== null)
1371
+ * .map(([, val]) => val);
1372
+ * ```
1373
+ *
1374
+ * @remarks
1375
+ * **Result Pattern**: Returns `[error, null] | [null, result]` (NEVER THROWS)
1376
+ * - Eliminates try-catch blocks
1377
+ * - Makes error handling explicit and type-safe
1378
+ * - Works well with destructuring
1379
+ * - Inspired by Go's error handling
1380
+ *
1381
+ * **When to use `tryCatchSync()` vs `wrap()` vs `tryCatch()`**:
1382
+ * - Use `tryCatchSync()`: SYNC operations, prefer explicit errors (no exceptions)
1383
+ * - Use `tryCatch()`: ASYNC operations, prefer explicit errors (no exceptions)
1384
+ * - Use `wrap()`: ASYNC operations, want traditional exceptions to bubble up
1385
+ *
1386
+ * **Pattern Comparison**:
1387
+ * ```typescript
1388
+ * // tryCatchSync: SYNC + RETURNS error
1389
+ * const [error, result] = BaseException.tryCatchSync(() =>
1390
+ * JSON.parse(jsonString)
1391
+ * );
1392
+ * if (error) {
1393
+ * console.error('Parse failed:', error.message);
1394
+ * return defaultValue;
1395
+ * }
1396
+ * return result;
1397
+ *
1398
+ * // tryCatch: ASYNC + RETURNS error
1399
+ * const [error, data] = await BaseException.tryCatch(async () =>
1400
+ * fetch('/api/data').then(r => r.json())
1401
+ * );
1402
+ * if (error) return handleError(error);
1403
+ * return processData(data);
1404
+ *
1405
+ * // wrap: ASYNC + THROWS error
1406
+ * try {
1407
+ * const data = await BaseException.wrap(
1408
+ * async () => fetch('/api/data').then(r => r.json())
1409
+ * );
1410
+ * return processData(data);
1411
+ * } catch (error) {
1412
+ * return handleError(error); // Must use try/catch
1413
+ * }
1414
+ * ```
1415
+ *
1416
+ * **Advantages over try/catch**:
1417
+ * - No nested blocks (flatter code)
1418
+ * - Error variable is properly scoped
1419
+ * - TypeScript narrows types correctly
1420
+ * - Works great with early returns
1421
+ * - Composable (map over arrays, etc.)
1422
+ *
1423
+ * @see {@link tryCatch} - Async version
1424
+ * @see {@link wrap} - Async version that throws instead of returning tuple
1425
+ */
1426
+ static tryCatchSync<TResult, TDetails = unknown, TCause = unknown>(this: BaseExceptionConstructor<TDetails, BaseException<TDetails>>, operation: () => TResult, options?: BaseExceptionOptions<TDetails, TCause>): [BaseException<TDetails>, null] | [null, TResult];
1427
+ /**
1428
+ * Safely executes an async operation and returns a Result tuple Promise.
1429
+ *
1430
+ * Async version of `tryCatchSync`. Returns a Promise that resolves to a tuple of
1431
+ * `[error, null]` if the operation fails, or `[null, result]` if it succeeds.\
1432
+ * Eliminates async try-catch blocks and makes async error handling explicit.
1433
+ *
1434
+ * **Error Handling Pattern**: Returns error tuples (Go/Rust-style)
1435
+ *
1436
+ * **Key Differences from Related Methods**:
1437
+ * - **`tryCatch(operation, options)`** ← YOU ARE HERE
1438
+ * - **ASYNC** (returns Promise<[error, null] | [null, result]>)
1439
+ * - **RETURNS** error tuple (Result/Either pattern)
1440
+ * - Use when: Async operations, explicit error handling
1441
+ * - Pattern: `const [error, result] = await tryCatch(...)`
1442
+ *
1443
+ * - **`tryCatchSync(operation, options)`** ← Sync version
1444
+ * - **SYNC** (returns [error, null] | [null, result] immediately)
1445
+ * - **RETURNS** error tuple (same pattern, synchronous)
1446
+ * - Use when: Synchronous operations, explicit error handling
1447
+ * - Pattern: `const [error, result] = tryCatchSync(...)`
1448
+ *
1449
+ * - **`wrap(operation, options)`** ← Different error pattern
1450
+ * - **ASYNC ONLY**
1451
+ * - **THROWS** BaseException on error (traditional exceptions)
1452
+ * - Use when: You want errors to bubble up to handlers
1453
+ * - Pattern: `try { await wrap(...) } catch (e) { ... }`
1454
+ *
1455
+ * **Type-safe**: When called on subclasses, returns that subclass type in the error position.
1456
+ *
1457
+ * @template TResult - The return type of the async operation
1458
+ * @template TDetails - Type of exception details
1459
+ * @template TCause - Type of exception cause
1460
+ * @param this - The exception constructor (auto-bound)
1461
+ * @param operation - Async function to execute
1462
+ * @param options - Optional exception metadata if an error occurs
1463
+ * @returns Promise resolving to tuple of `[error, null]` or `[null, result]` (never throws)
1464
+ *
1465
+ * @example
1466
+ * ```typescript
1467
+ * // Basic async operation
1468
+ * const [error, data] = await BaseException.tryCatch(async () => {
1469
+ * const response = await fetch('/api/users');
1470
+ * return response.json();
1471
+ * });
1472
+ *
1473
+ * if (error) {
1474
+ * console.error('API call failed:', error.message);
1475
+ * return;
1476
+ * }
1477
+ *
1478
+ * console.log('Users:', data);
1479
+ * ```
1480
+ *
1481
+ * @example
1482
+ * ```typescript
1483
+ * // With error metadata
1484
+ * const [error, user] = await BaseException.tryCatch(
1485
+ * async () => db.users.findById(userId),
1486
+ * {
1487
+ * code: 'DB_ERROR',
1488
+ * statusCode: 500,
1489
+ * details: { operation: 'findUser', userId }
1490
+ * }
1491
+ * );
1492
+ * ```
1493
+ *
1494
+ * @example
1495
+ * ```typescript
1496
+ * // Async operation chain
1497
+ * async function processUserData(userId: number) {
1498
+ * const [userError, user] = await BaseException.tryCatch(() =>
1499
+ * fetchUser(userId)
1500
+ * );
1501
+ * if (userError) return { error: userError };
1502
+ *
1503
+ * const [ordersError, orders] = await BaseException.tryCatch(() =>
1504
+ * fetchOrders(user.id)
1505
+ * );
1506
+ * if (ordersError) return { error: ordersError };
1507
+ *
1508
+ * return { data: { user, orders } };
1509
+ * }
1510
+ * ```
1511
+ *
1512
+ * @example
1513
+ * ```typescript
1514
+ * // Parallel operations
1515
+ * const results = await Promise.all([
1516
+ * BaseException.tryCatch(() => fetchUsers()),
1517
+ * BaseException.tryCatch(() => fetchPosts()),
1518
+ * BaseException.tryCatch(() => fetchComments())
1519
+ * ]);
1520
+ *
1521
+ * const [usersResult, postsResult, commentsResult] = results;
1522
+ *
1523
+ * const allSucceeded = results.every(([err]) => err === null);
1524
+ * if (allSucceeded) {
1525
+ * const [, users] = usersResult;
1526
+ * const [, posts] = postsResult;
1527
+ * const [, comments] = commentsResult;
1528
+ * // All operations succeeded
1529
+ * }
1530
+ * ```
1531
+ *
1532
+ * @example
1533
+ * ```typescript
1534
+ * // With custom exception class
1535
+ * class ApiException extends BaseException {
1536
+ * // Custom properties/methods
1537
+ * }
1538
+ *
1539
+ * const [error, response] = await ApiException.tryCatch(
1540
+ * async () => callExternalAPI()
1541
+ * );
1542
+ *
1543
+ * if (error) {
1544
+ * // error is typed as ApiException
1545
+ * handleApiError(error);
1546
+ * }
1547
+ * ```
1548
+ *
1549
+ * @example
1550
+ * ```typescript
1551
+ * // API endpoint handler
1552
+ * app.get('/users/:id', async (req, res) => {
1553
+ * const [error, user] = await BaseException.tryCatch(
1554
+ * async () => userService.getById(req.params.id),
1555
+ * { code: 'USER_FETCH_ERROR' }
1556
+ * );
1557
+ *
1558
+ * if (error) {
1559
+ * return res.status(error.statusCode || 500).json(
1560
+ * error.toJSON()
1561
+ * );
1562
+ * }
1563
+ *
1564
+ * res.json(user);
1565
+ * });
1566
+ * ```
1567
+ *
1568
+ * @remarks
1569
+ * **Async Result Pattern**: Returns `Promise<[error, null] | [null, result]>`
1570
+ * - Eliminates async/await try-catch blocks
1571
+ * - Makes async error handling explicit and type-safe
1572
+ * - Works perfectly with Promise.all() for parallel operations
1573
+ * - Great for API handlers and service methods
1574
+ *
1575
+ * @see {@link tryCatchSync} - Synchronous version
1576
+ * @see {@link wrap} - Throws on error instead of returning tuple
1577
+ * @see {@link from} - The conversion method used internally
1578
+ */
1579
+ static tryCatch<TResult, TDetails = unknown, TCause = unknown>(this: BaseExceptionConstructor<TDetails, BaseException<TDetails>>, operation: () => Promise<TResult>, options?: BaseExceptionOptions<TDetails, TCause>): Promise<[BaseException<TDetails>, null] | [null, TResult]>;
1580
+ }
1581
+ /**
1582
+ * Configuration options for creating or customizing a BaseException.
1583
+ *
1584
+ * This interface defines the optional metadata that can be provided when creating
1585
+ * exceptions via the constructor, `from()`, or other factory methods. All properties
1586
+ * are optional, allowing flexible error creation with varying levels of detail.
1587
+ *
1588
+ * @template TDetails - Type of the structured details object.
1589
+ * @template TCause - Type of the structured cause
1590
+ *
1591
+ * @example
1592
+ * ```typescript
1593
+ * // Basic options
1594
+ * const options: BaseExceptionOptions = {
1595
+ * code: 'USER_NOT_FOUND',
1596
+ * statusCode: 404
1597
+ * };
1598
+ *
1599
+ * const error = new BaseException('User not found', options);
1600
+ * ```
1601
+ *
1602
+ * @example
1603
+ * ```typescript
1604
+ * // With typed details
1605
+ * interface PaymentDetails {
1606
+ * transactionId: string;
1607
+ * amount: number;
1608
+ * currency: string;
1609
+ * }
1610
+ *
1611
+ * const options: BaseExceptionOptions<PaymentDetails> = {
1612
+ * code: 'PAYMENT_FAILED',
1613
+ * statusCode: 402,
1614
+ * details: {
1615
+ * transactionId: 'tx_12345',
1616
+ * amount: 99.99,
1617
+ * currency: 'USD'
1618
+ * }
1619
+ * };
1620
+ *
1621
+ * const error = new BaseException<PaymentDetails>('Payment processing failed', options);
1622
+ * ```
1623
+ *
1624
+ * @example
1625
+ * ```typescript
1626
+ * // With error chain (cause)
1627
+ * try {
1628
+ * await database.connect();
1629
+ * } catch (dbError) {
1630
+ * throw new BaseException('Failed to connect to database', {
1631
+ * code: 'DB_CONNECTION_ERROR',
1632
+ * statusCode: 503,
1633
+ * cause: dbError, // Preserves the original error
1634
+ * details: {
1635
+ * host: 'localhost',
1636
+ * port: 5432
1637
+ * }
1638
+ * });
1639
+ * }
1640
+ * ```
1641
+ *
1642
+ * @example
1643
+ * ```typescript
1644
+ * // With custom timestamp (for replaying/testing)
1645
+ * const options: BaseExceptionOptions = {
1646
+ * code: 'TEST_ERROR',
1647
+ * timestamp: new Date('2024-01-15T10:30:00Z'), // Specific timestamp
1648
+ * details: { testCase: 'scenario-1' }
1649
+ * };
1650
+ * ```
1651
+ *
1652
+ * @example
1653
+ * ```typescript
1654
+ * // Using fallbackMessage for error conversion
1655
+ * const unknownError: unknown = someOperation();
1656
+ *
1657
+ * const error = BaseException.from(unknownError, {
1658
+ * code: 'OPERATION_FAILED',
1659
+ * fallbackMessage: 'An unknown error occurred', // Used if error has no message
1660
+ * statusCode: 500
1661
+ * });
1662
+ * ```
1663
+ */
1664
+ export interface BaseExceptionOptions<TDetails = unknown, TCause = unknown> {
1665
+ /**
1666
+ * Application-specific error code for categorizing errors.
1667
+ *
1668
+ * Error codes should be unique, uppercase strings with underscores (e.g., `USER_NOT_FOUND`).
1669
+ * They're useful for:
1670
+ * - Error categorization and filtering
1671
+ * - Client-side error handling logic
1672
+ * - Logging and monitoring
1673
+ * - Internationalization (mapping codes to localized messages)
1674
+ *
1675
+ * @example
1676
+ * ```typescript
1677
+ * { code: 'VALIDATION_ERROR' }
1678
+ * { code: 'AUTH_TOKEN_EXPIRED' }
1679
+ * { code: 'DB_CONSTRAINT_VIOLATION' }
1680
+ * ```
1681
+ */
1682
+ code?: string;
1683
+ /**
1684
+ * HTTP status code associated with this exception.
1685
+ *
1686
+ * Use standard HTTP status codes to indicate the type of error:
1687
+ * - `400-499`: Client errors (bad request, unauthorized, not found, etc.)
1688
+ * - `500-599`: Server errors (internal error, service unavailable, etc.)
1689
+ *
1690
+ * Defaults to `500` (Internal Server Error) when creating exceptions via `from()`.
1691
+ *
1692
+ * @example
1693
+ * ```typescript
1694
+ * { statusCode: 400 } // Bad Request
1695
+ * { statusCode: 401 } // Unauthorized
1696
+ * { statusCode: 404 } // Not Found
1697
+ * { statusCode: 409 } // Conflict
1698
+ * { statusCode: 500 } // Internal Server Error
1699
+ * { statusCode: 503 } // Service Unavailable
1700
+ * ```
1701
+ */
1702
+ statusCode?: number;
1703
+ /**
1704
+ * Structured additional details about the exception.
1705
+ *
1706
+ * Use this to include any domain-specific context that helps with:
1707
+ * - Debugging (input values, state information)
1708
+ * - Client-side handling (field-specific validation errors)
1709
+ * - Logging and monitoring (request IDs, user context)
1710
+ * - Error recovery (retry information, alternative actions)
1711
+ *
1712
+ * The type is generic to ensure type safety for your specific error details.
1713
+ *
1714
+ * @example
1715
+ * ```typescript
1716
+ * // Validation details
1717
+ * {
1718
+ * details: {
1719
+ * field: 'email',
1720
+ * value: 'invalid-email',
1721
+ * rule: 'email_format'
1722
+ * }
1723
+ * }
1724
+ * ```
1725
+ *
1726
+ * @example
1727
+ * ```typescript
1728
+ * // API error details
1729
+ * {
1730
+ * details: {
1731
+ * endpoint: '/api/users',
1732
+ * method: 'POST',
1733
+ * requestId: 'req_abc123',
1734
+ * responseTime: 523
1735
+ * }
1736
+ * }
1737
+ * ```
1738
+ *
1739
+ * @example
1740
+ * ```typescript
1741
+ * // Database error details
1742
+ * {
1743
+ * details: {
1744
+ * query: 'SELECT * FROM users WHERE id = ?',
1745
+ * table: 'users',
1746
+ * constraint: 'users_email_unique'
1747
+ * }
1748
+ * }
1749
+ * ```
1750
+ */
1751
+ details?: BaseExceptionDetails<TDetails>;
1752
+ /**
1753
+ * The underlying error that caused this exception.
1754
+ *
1755
+ * Preserves the error chain for debugging. This is automatically set when using
1756
+ * `from()` to convert errors. The cause is:
1757
+ * - Preserved in the exception's `cause` property
1758
+ * - Included in `toJSON()` output (serialized recursively)
1759
+ * - Available for logging and error tracking
1760
+ *
1761
+ * Follows the standard Error.cause pattern from ECMAScript.
1762
+ *
1763
+ * @example
1764
+ * ```typescript
1765
+ * try {
1766
+ * const data = JSON.parse(invalidJson);
1767
+ * } catch (parseError) {
1768
+ * throw new BaseException('Failed to parse configuration', {
1769
+ * cause: parseError // Original SyntaxError preserved
1770
+ * });
1771
+ * }
1772
+ * ```
1773
+ *
1774
+ * @example
1775
+ * ```typescript
1776
+ * // Error chain
1777
+ * const networkError = new Error('ETIMEDOUT');
1778
+ * const apiError = new BaseException('API request failed', {
1779
+ * cause: networkError
1780
+ * });
1781
+ * const appError = new BaseException('User fetch failed', {
1782
+ * cause: apiError
1783
+ * });
1784
+ *
1785
+ * // The entire chain is preserved and can be serialized
1786
+ * console.log(appError.toJSON().cause);
1787
+ * // Includes apiError with its cause networkError
1788
+ * ```
1789
+ */
1790
+ cause?: TCause;
1791
+ /**
1792
+ * Custom timestamp for when the exception occurred.
1793
+ *
1794
+ * Defaults to `new Date()` when the exception is created. Override this when:
1795
+ * - Replaying/reconstructing exceptions from logs
1796
+ * - Testing with fixed timestamps
1797
+ * - Forwarding exceptions from other systems
1798
+ *
1799
+ * @example
1800
+ * ```typescript
1801
+ * // Reconstructing from logs
1802
+ * {
1803
+ * timestamp: new Date('2024-01-15T10:30:45.123Z')
1804
+ * }
1805
+ * ```
1806
+ *
1807
+ * @example
1808
+ * ```typescript
1809
+ * // Fixed timestamp for testing
1810
+ * {
1811
+ * timestamp: new Date('2024-01-01T00:00:00Z')
1812
+ * }
1813
+ * ```
1814
+ */
1815
+ timestamp?: Date;
1816
+ /**
1817
+ * Fallback message to use if the error source has no message.
1818
+ *
1819
+ * Only used by `from()` and related methods when converting errors that don't
1820
+ * have a message property. Useful for providing context-specific default messages.
1821
+ *
1822
+ * @example
1823
+ * ```typescript
1824
+ * // Converting unknown errors
1825
+ * const error = BaseException.from(unknownValue, {
1826
+ * fallbackMessage: 'Database operation failed',
1827
+ * code: 'DB_ERROR'
1828
+ * });
1829
+ * // If unknownValue has no message, "Database operation failed" is used
1830
+ * ```
1831
+ *
1832
+ * @example
1833
+ * ```typescript
1834
+ * // Context-specific fallbacks
1835
+ * function wrapApiCall(apiError: unknown) {
1836
+ * return BaseException.from(apiError, {
1837
+ * fallbackMessage: 'External API request failed',
1838
+ * code: 'API_ERROR'
1839
+ * });
1840
+ * }
1841
+ * ```
1842
+ */
1843
+ fallbackMessage?: string;
1844
+ }
1845
+ /**
1846
+ * Type representing a constructor (class) for BaseException or its subclasses.
1847
+ *
1848
+ * This is a **constructor type**, not an instance type. It represents the type of the
1849
+ * class itself (the constructor function) rather than instances created by the class.
1850
+ *
1851
+ * This type is essential for proper TypeScript typing of static methods that need to
1852
+ * reference `this` in a way that works correctly with inheritance. When a static method
1853
+ * uses `this: BaseExceptionConstructor<TDetails, TException>`, TypeScript understands
1854
+ * that `this` refers to the constructor being called, enabling proper type inference
1855
+ * for subclasses.
1856
+ *
1857
+ * @template TDetails - The type of the exception details object.
1858
+ * @template TException - The type of exception instance the constructor creates. Must extend BaseException<TDetails>.
1859
+ *
1860
+ * @example
1861
+ * ```typescript
1862
+ * // Basic usage in a static method
1863
+ * class MyException extends BaseException {
1864
+ * static customMethod<
1865
+ * TDetails = unknown,
1866
+ * T extends BaseException<TDetails> = BaseException<TDetails>
1867
+ * >(
1868
+ * this: BaseExceptionConstructor<TDetails, T>,
1869
+ * message: string
1870
+ * ): T {
1871
+ * return new this(message); // `this` correctly refers to the constructor
1872
+ * }
1873
+ * }
1874
+ * ```
1875
+ *
1876
+ * @example
1877
+ * ```typescript
1878
+ * // Why this is needed: Without proper typing, subclass static methods lose type information
1879
+ * interface ApiErrorDetails {
1880
+ * endpoint: string;
1881
+ * }
1882
+ *
1883
+ * class ApiException extends BaseException<ApiErrorDetails> {
1884
+ * // Using BaseExceptionConstructor ensures proper typing
1885
+ * }
1886
+ *
1887
+ * // When calling ApiException.from(), TypeScript knows the return type is ApiException
1888
+ * const ex = ApiException.from(new Error('API failed'));
1889
+ * // ex is typed as ApiException, not BaseException
1890
+ * ```
1891
+ *
1892
+ *
1893
+ * @remarks
1894
+ * This follows TypeScript's convention for constructor types, similar to built-in types
1895
+ * like `ErrorConstructor`, `PromiseConstructor`, etc.
1896
+ *
1897
+ * @see {@link BaseException.from} - Uses this type for proper subclass typing
1898
+ * @see {@link BaseException.create} - Uses this type for proper subclass typing
1899
+ * @see {@link BaseException.createFromError} - Uses this type for proper subclass typing
1900
+ */
1901
+ export type BaseExceptionConstructor<TDetails = unknown, TException extends BaseException<TDetails> = BaseException<TDetails>, TCause = unknown> = new (message: string, options?: BaseExceptionOptions<TDetails, TCause>) => TException;
1902
+ /**
1903
+ * Helper type for exception details.
1904
+ * Defaults to a loose dictionary if no specific type is provided.
1905
+ */
1906
+ export type BaseExceptionDetails<TDetails = unknown> = unknown extends TDetails ? Record<string, any> : {
1907
+ [K in keyof TDetails as TDetails[K] extends (...args: unknown[]) => unknown ? never : K]: TDetails[K];
1908
+ };