reslib 1.0.3 → 2.0.0

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