@pencroff-lab/kore 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/src/types/err.d.ts +1116 -0
- package/dist/cjs/src/types/err.d.ts.map +1 -0
- package/dist/cjs/src/types/err.js +1324 -0
- package/dist/cjs/src/types/err.js.map +1 -0
- package/dist/cjs/src/types/index.d.ts +3 -0
- package/dist/cjs/src/types/index.d.ts.map +1 -0
- package/dist/cjs/src/types/index.js +19 -0
- package/dist/cjs/src/types/index.js.map +1 -0
- package/dist/cjs/src/types/outcome.d.ts +1002 -0
- package/dist/cjs/src/types/outcome.d.ts.map +1 -0
- package/dist/cjs/src/types/outcome.js +958 -0
- package/dist/cjs/src/types/outcome.js.map +1 -0
- package/dist/cjs/src/utils/format_dt.d.ts +9 -0
- package/dist/cjs/src/utils/format_dt.d.ts.map +1 -0
- package/dist/cjs/src/utils/format_dt.js +29 -0
- package/dist/cjs/src/utils/format_dt.js.map +1 -0
- package/dist/cjs/src/utils/index.d.ts +2 -0
- package/dist/cjs/src/utils/index.d.ts.map +1 -0
- package/dist/cjs/src/utils/index.js +18 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/src/types/err.d.ts +1116 -0
- package/dist/esm/src/types/err.d.ts.map +1 -0
- package/dist/esm/src/types/err.js +1320 -0
- package/dist/esm/src/types/err.js.map +1 -0
- package/dist/esm/src/types/index.d.ts +3 -0
- package/dist/esm/src/types/index.d.ts.map +1 -0
- package/dist/esm/src/types/index.js +3 -0
- package/dist/esm/src/types/index.js.map +1 -0
- package/dist/esm/src/types/outcome.d.ts +1002 -0
- package/dist/esm/src/types/outcome.d.ts.map +1 -0
- package/dist/esm/src/types/outcome.js +954 -0
- package/dist/esm/src/types/outcome.js.map +1 -0
- package/dist/esm/src/utils/format_dt.d.ts +9 -0
- package/dist/esm/src/utils/format_dt.d.ts.map +1 -0
- package/dist/esm/src/utils/format_dt.js +26 -0
- package/dist/esm/src/utils/format_dt.js.map +1 -0
- package/dist/esm/src/utils/index.d.ts +2 -0
- package/dist/esm/src/utils/index.d.ts.map +1 -0
- package/dist/esm/src/utils/index.js +2 -0
- package/dist/esm/src/utils/index.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Error-as-value implementation for TypeScript applications.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a Go-style error handling approach where errors are
|
|
5
|
+
* passed as values rather than thrown as exceptions. The `Err` class supports
|
|
6
|
+
* both single error wrapping with context and error aggregation.
|
|
7
|
+
*
|
|
8
|
+
* ## Immutability Contract
|
|
9
|
+
*
|
|
10
|
+
* All `Err` instances are immutable. Methods that appear to modify an error
|
|
11
|
+
* (like `wrap`, `withCode`, `withMetadata`, `add`) return new instances.
|
|
12
|
+
* The original error is never mutated. This means:
|
|
13
|
+
*
|
|
14
|
+
* - Safe to pass errors across boundaries without defensive copying
|
|
15
|
+
* - Method chaining always produces new instances
|
|
16
|
+
* - No "spooky action at a distance" bugs
|
|
17
|
+
*
|
|
18
|
+
* @example Basic usage with tuple pattern
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { Err } from './err';
|
|
21
|
+
*
|
|
22
|
+
* function divide(a: number, b: number): [number, null] | [null, Err] {
|
|
23
|
+
* if (b === 0) {
|
|
24
|
+
* return [null, Err.from('Division by zero', 'MATH_ERROR')];
|
|
25
|
+
* }
|
|
26
|
+
* return [a / b, null];
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* const [result, err] = divide(10, 0);
|
|
30
|
+
* if (err) {
|
|
31
|
+
* console.error(err.toString());
|
|
32
|
+
* return;
|
|
33
|
+
* }
|
|
34
|
+
* console.log(result); // result is number here
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Error wrapping with context
|
|
38
|
+
* ```typescript
|
|
39
|
+
* function readConfig(path: string): [Config, null] | [null, Err] {
|
|
40
|
+
* const [content, readErr] = readFile(path);
|
|
41
|
+
* if (readErr) {
|
|
42
|
+
* return [null, readErr.wrap(`Failed to read config from ${path}`)];
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* const [parsed, parseErr] = parseJSON(content);
|
|
46
|
+
* if (parseErr) {
|
|
47
|
+
* return [null, parseErr
|
|
48
|
+
* .wrap('Invalid config format')
|
|
49
|
+
* .withCode('CONFIG_ERROR')
|
|
50
|
+
* .withMetadata({ path })];
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* return [parsed as Config, null];
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example Static wrap for catching native errors
|
|
58
|
+
* ```typescript
|
|
59
|
+
* function parseData(raw: string): [Data, null] | [null, Err] {
|
|
60
|
+
* try {
|
|
61
|
+
* return [JSON.parse(raw), null];
|
|
62
|
+
* } catch (e) {
|
|
63
|
+
* return [null, Err.wrap('Failed to parse data', e as Error)];
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Aggregating multiple errors
|
|
69
|
+
* ```typescript
|
|
70
|
+
* function validateUser(input: UserInput): [User, null] | [null, Err] {
|
|
71
|
+
* let errors = Err.aggregate('Validation failed');
|
|
72
|
+
*
|
|
73
|
+
* if (!input.name?.trim()) {
|
|
74
|
+
* errors = errors.add('Name is required');
|
|
75
|
+
* }
|
|
76
|
+
* if (!input.email?.includes('@')) {
|
|
77
|
+
* errors = errors.add(Err.from('Invalid email', 'INVALID_EMAIL'));
|
|
78
|
+
* }
|
|
79
|
+
* if (input.age !== undefined && input.age < 0) {
|
|
80
|
+
* errors = errors.add('Age cannot be negative');
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* if (errors.count > 0) {
|
|
84
|
+
* return [null, errors.withCode('VALIDATION_ERROR')];
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* return [input as User, null];
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example Serialization for service-to-service communication
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // Backend: serialize error for API response
|
|
94
|
+
* const err = Err.from('User not found', 'NOT_FOUND');
|
|
95
|
+
* res.status(404).json({ error: err.toJSON() });
|
|
96
|
+
*
|
|
97
|
+
* // Frontend: deserialize error from API response
|
|
98
|
+
* const response = await fetch('/api/user/123');
|
|
99
|
+
* if (!response.ok) {
|
|
100
|
+
* const { error } = await response.json();
|
|
101
|
+
* const err = Err.fromJSON(error);
|
|
102
|
+
* console.log(err.code); // 'NOT_FOUND'
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* // Public API: omit stack traces
|
|
106
|
+
* res.json({ error: err.toJSON({ stack: false }) });
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @module err
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Error code type - typically uppercase snake_case identifiers.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const codes: ErrCode[] = [
|
|
117
|
+
* 'NOT_FOUND',
|
|
118
|
+
* 'VALIDATION_ERROR',
|
|
119
|
+
* 'DB_CONNECTION_FAILED',
|
|
120
|
+
* 'AUTH_EXPIRED',
|
|
121
|
+
* ];
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export type ErrCode = string;
|
|
125
|
+
/**
|
|
126
|
+
* Options for creating or modifying an Err instance.
|
|
127
|
+
*/
|
|
128
|
+
export interface ErrOptions {
|
|
129
|
+
/** Error code for programmatic error handling */
|
|
130
|
+
code?: ErrCode;
|
|
131
|
+
/** Human-readable error message */
|
|
132
|
+
message?: string;
|
|
133
|
+
/** Additional contextual data */
|
|
134
|
+
metadata?: Record<string, unknown>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Options for JSON serialization.
|
|
138
|
+
*/
|
|
139
|
+
export interface ErrJSONOptions {
|
|
140
|
+
/**
|
|
141
|
+
* Include stack trace in output.
|
|
142
|
+
* Set to `false` for public API responses.
|
|
143
|
+
* @default true
|
|
144
|
+
*/
|
|
145
|
+
stack?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Include metadata in output.
|
|
148
|
+
* Set to `false` to omit potentially sensitive data.
|
|
149
|
+
* @default true
|
|
150
|
+
*/
|
|
151
|
+
metadata?: boolean;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Options for toString() output formatting.
|
|
155
|
+
*/
|
|
156
|
+
export interface ToStringOptions {
|
|
157
|
+
/**
|
|
158
|
+
* Include stack trace in output.
|
|
159
|
+
* - `true`: Include full stack trace
|
|
160
|
+
* - `number`: Include only top N frames (default: 3 when number)
|
|
161
|
+
* @default undefined (no stack)
|
|
162
|
+
*/
|
|
163
|
+
stack?: boolean | number;
|
|
164
|
+
/**
|
|
165
|
+
* Include timestamp in output (ISO 8601 format).
|
|
166
|
+
* @default false
|
|
167
|
+
*/
|
|
168
|
+
date?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Include metadata object in output.
|
|
171
|
+
* @default false
|
|
172
|
+
*/
|
|
173
|
+
metadata?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Maximum depth for cause chain traversal.
|
|
176
|
+
* When exceeded, shows "... (N more causes)".
|
|
177
|
+
* @default undefined (unlimited)
|
|
178
|
+
*/
|
|
179
|
+
maxDepth?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Indentation string for nested output.
|
|
182
|
+
* @default " " (two spaces)
|
|
183
|
+
*/
|
|
184
|
+
indent?: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* JSON representation of an Err for serialization.
|
|
188
|
+
*/
|
|
189
|
+
export interface ErrJSON {
|
|
190
|
+
message: string;
|
|
191
|
+
kind?: "Err";
|
|
192
|
+
isErr?: boolean;
|
|
193
|
+
code?: ErrCode;
|
|
194
|
+
metadata?: Record<string, unknown>;
|
|
195
|
+
timestamp: string;
|
|
196
|
+
stack?: string;
|
|
197
|
+
cause?: ErrJSON;
|
|
198
|
+
errors: ErrJSON[];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A value-based error type that supports wrapping and aggregation.
|
|
202
|
+
*
|
|
203
|
+
* `Err` is designed to be returned from functions instead of throwing exceptions,
|
|
204
|
+
* following the Go-style error handling pattern. It supports:
|
|
205
|
+
*
|
|
206
|
+
* - **Single errors**: Created via `Err.from()` with optional code and metadata
|
|
207
|
+
* - **Error wrapping**: Adding context to errors as they propagate up the call stack
|
|
208
|
+
* - **Error aggregation**: Collecting multiple errors under a single parent (e.g., validation)
|
|
209
|
+
* - **Serialization**: Convert to/from JSON for service-to-service communication
|
|
210
|
+
*
|
|
211
|
+
* All instances are immutable - methods return new instances rather than mutating.
|
|
212
|
+
*
|
|
213
|
+
* @example Creating errors
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // From string with code (most common)
|
|
216
|
+
* const err1 = Err.from('User not found', 'NOT_FOUND');
|
|
217
|
+
*
|
|
218
|
+
* // From string with full options
|
|
219
|
+
* const err2 = Err.from('Connection timeout', {
|
|
220
|
+
* code: 'TIMEOUT',
|
|
221
|
+
* metadata: { host: 'api.example.com' }
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* // From native Error (preserves original stack and cause chain)
|
|
225
|
+
* try {
|
|
226
|
+
* riskyOperation();
|
|
227
|
+
* } catch (e) {
|
|
228
|
+
* const err = Err.from(e).withCode('OPERATION_FAILED');
|
|
229
|
+
* return [null, err];
|
|
230
|
+
* }
|
|
231
|
+
*
|
|
232
|
+
* // From unknown (safe for catch blocks)
|
|
233
|
+
* const err3 = Err.from(unknownValue);
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @example Wrapping errors with static method
|
|
237
|
+
* ```typescript
|
|
238
|
+
* try {
|
|
239
|
+
* await db.query(sql);
|
|
240
|
+
* } catch (e) {
|
|
241
|
+
* return [null, Err.wrap('Database query failed', e as Error)];
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* @example Aggregating errors
|
|
246
|
+
* ```typescript
|
|
247
|
+
* let errors = Err.aggregate('Multiple operations failed')
|
|
248
|
+
* .add(Err.from('Database write failed'))
|
|
249
|
+
* .add(Err.from('Cache invalidation failed'))
|
|
250
|
+
* .add('Notification send failed'); // strings are auto-wrapped
|
|
251
|
+
*
|
|
252
|
+
* console.log(errors.count); // 3
|
|
253
|
+
* console.log(errors.flatten()); // Array of all individual errors
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export declare class Err {
|
|
257
|
+
/**
|
|
258
|
+
* Discriminator property for type narrowing.
|
|
259
|
+
* Always "Err" for Err instances.
|
|
260
|
+
*/
|
|
261
|
+
readonly kind: "Err";
|
|
262
|
+
/**
|
|
263
|
+
* Discriminator property for type narrowing.
|
|
264
|
+
* Always `true` for Err instances.
|
|
265
|
+
*
|
|
266
|
+
* Useful when checking values from external sources (API responses,
|
|
267
|
+
* message queues) where `instanceof` may not work.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* // Checking unknown values from API
|
|
272
|
+
* const data = await response.json();
|
|
273
|
+
* if (data.error?.isErr) {
|
|
274
|
+
* // Likely an Err-like object
|
|
275
|
+
* }
|
|
276
|
+
*
|
|
277
|
+
* // For type narrowing, prefer Err.isErr()
|
|
278
|
+
* if (Err.isErr(value)) {
|
|
279
|
+
* console.error(value.message);
|
|
280
|
+
* }
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
readonly isErr: true;
|
|
284
|
+
/** Human-readable error message */
|
|
285
|
+
readonly message: string;
|
|
286
|
+
/** Error code for programmatic handling */
|
|
287
|
+
readonly code?: ErrCode;
|
|
288
|
+
/** Additional contextual data */
|
|
289
|
+
readonly metadata?: Record<string, unknown>;
|
|
290
|
+
/**
|
|
291
|
+
* Timestamp when the error was created (ISO 8601 string).
|
|
292
|
+
*
|
|
293
|
+
* Stored as string for easy serialization and comparison.
|
|
294
|
+
*/
|
|
295
|
+
readonly timestamp: string;
|
|
296
|
+
/** The wrapped/caused error (for error chains) */
|
|
297
|
+
private readonly _cause?;
|
|
298
|
+
/** List of aggregated errors */
|
|
299
|
+
private readonly _errors;
|
|
300
|
+
/**
|
|
301
|
+
* Stack trace - either from original Error or captured at creation.
|
|
302
|
+
*
|
|
303
|
+
* When wrapping a native Error, this preserves the original stack
|
|
304
|
+
* for better debugging (points to actual error location).
|
|
305
|
+
*/
|
|
306
|
+
private readonly _stack?;
|
|
307
|
+
/**
|
|
308
|
+
* Private constructor - use static factory methods instead.
|
|
309
|
+
* @internal
|
|
310
|
+
*/
|
|
311
|
+
private constructor();
|
|
312
|
+
/**
|
|
313
|
+
* Create an Err from a string message with optional code.
|
|
314
|
+
*
|
|
315
|
+
* @param message - Error message
|
|
316
|
+
* @param code - Optional error code
|
|
317
|
+
* @returns New Err instance
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```typescript
|
|
321
|
+
* const err = Err.from('User not found', 'NOT_FOUND');
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
static from(message: string, code?: ErrCode): Err;
|
|
325
|
+
/**
|
|
326
|
+
* Create an Err from a string message with full options.
|
|
327
|
+
*
|
|
328
|
+
* @param message - Error message
|
|
329
|
+
* @param options - Code and metadata options
|
|
330
|
+
* @returns New Err instance
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const err = Err.from('Connection timeout', {
|
|
335
|
+
* code: 'TIMEOUT',
|
|
336
|
+
* metadata: { host: 'api.example.com', timeoutMs: 5000 }
|
|
337
|
+
* });
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
static from(message: string, options: ErrOptions): Err;
|
|
341
|
+
/**
|
|
342
|
+
* Create an Err from a native Error.
|
|
343
|
+
*
|
|
344
|
+
* Preserves the original error's:
|
|
345
|
+
* - Stack trace (as primary stack for debugging)
|
|
346
|
+
* - Cause chain (if `error.cause` is Error or string)
|
|
347
|
+
* - Name (in metadata as `originalName`)
|
|
348
|
+
*
|
|
349
|
+
* @param error - Native Error instance
|
|
350
|
+
* @param options - Optional overrides for message, code, and metadata
|
|
351
|
+
* @returns New Err instance
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* try {
|
|
356
|
+
* JSON.parse(invalidJson);
|
|
357
|
+
* } catch (e) {
|
|
358
|
+
* return Err.from(e as Error, { code: 'PARSE_ERROR' });
|
|
359
|
+
* }
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
static from(error: Error, options?: ErrOptions): Err;
|
|
363
|
+
/**
|
|
364
|
+
* Create an Err from another Err instance (clone with optional overrides).
|
|
365
|
+
*
|
|
366
|
+
* @param error - Existing Err instance
|
|
367
|
+
* @param options - Optional overrides
|
|
368
|
+
* @returns New Err instance with merged properties
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```typescript
|
|
372
|
+
* const original = Err.from('Original error');
|
|
373
|
+
* const modified = Err.from(original, { code: 'NEW_CODE' });
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
static from(error: Err, options?: ErrOptions): Err;
|
|
377
|
+
/**
|
|
378
|
+
* Create an Err from an unknown value (safe for catch blocks).
|
|
379
|
+
*
|
|
380
|
+
* Handles any value that might be thrown, including non-Error objects,
|
|
381
|
+
* strings, numbers, null, and undefined.
|
|
382
|
+
*
|
|
383
|
+
* @param error - Any value
|
|
384
|
+
* @param options - Optional code and metadata
|
|
385
|
+
* @returns New Err instance
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* try {
|
|
390
|
+
* await riskyAsyncOperation();
|
|
391
|
+
* } catch (e) {
|
|
392
|
+
* // Safe - handles any thrown value
|
|
393
|
+
* return Err.from(e).wrap('Operation failed');
|
|
394
|
+
* }
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
static from(error: unknown, options?: ErrOptions): Err;
|
|
398
|
+
/**
|
|
399
|
+
* Static convenience method to wrap an error with a context message.
|
|
400
|
+
*
|
|
401
|
+
* Creates a new Err with the provided message, having the original
|
|
402
|
+
* error as its cause. This is the recommended pattern for catch blocks.
|
|
403
|
+
*
|
|
404
|
+
* @param message - Context message explaining what operation failed
|
|
405
|
+
* @param error - The original error (Err, Error, or string)
|
|
406
|
+
* @param options - Optional code and metadata for the wrapper
|
|
407
|
+
* @returns New Err instance with the original as cause
|
|
408
|
+
*
|
|
409
|
+
* @see {@link Err.prototype.wrap} for the instance method
|
|
410
|
+
*
|
|
411
|
+
* @example Basic usage in catch block
|
|
412
|
+
* ```typescript
|
|
413
|
+
* try {
|
|
414
|
+
* await db.query(sql);
|
|
415
|
+
* } catch (e) {
|
|
416
|
+
* return Err.wrap('Database query failed', e as Error);
|
|
417
|
+
* }
|
|
418
|
+
* ```
|
|
419
|
+
*
|
|
420
|
+
* @example With code and metadata
|
|
421
|
+
* ```typescript
|
|
422
|
+
* try {
|
|
423
|
+
* const user = await fetchUser(id);
|
|
424
|
+
* } catch (e) {
|
|
425
|
+
* return Err.wrap('Failed to fetch user', e as Error, {
|
|
426
|
+
* code: 'USER_FETCH_ERROR',
|
|
427
|
+
* metadata: { userId: id }
|
|
428
|
+
* });
|
|
429
|
+
* }
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
static wrap(message: string, error: Err | Error | string, options?: ErrOptions): Err;
|
|
433
|
+
/**
|
|
434
|
+
* Create an aggregate error for collecting multiple errors.
|
|
435
|
+
*
|
|
436
|
+
* Useful for validation, batch operations, or any scenario where
|
|
437
|
+
* multiple errors should be collected and reported together.
|
|
438
|
+
*
|
|
439
|
+
* @param message - Parent error message describing the aggregate
|
|
440
|
+
* @param errors - Optional initial list of errors
|
|
441
|
+
* @param options - Optional code and metadata for the aggregate
|
|
442
|
+
* @returns New aggregate Err instance
|
|
443
|
+
*
|
|
444
|
+
* @example Validation
|
|
445
|
+
* ```typescript
|
|
446
|
+
* function validate(data: Input): [Valid, null] | [null, Err] {
|
|
447
|
+
* let errors = Err.aggregate('Validation failed');
|
|
448
|
+
*
|
|
449
|
+
* if (!data.email) errors = errors.add('Email is required');
|
|
450
|
+
* if (!data.name) errors = errors.add('Name is required');
|
|
451
|
+
*
|
|
452
|
+
* if (errors.count > 0) {
|
|
453
|
+
* return [null, errors.withCode('VALIDATION_ERROR')];
|
|
454
|
+
* }
|
|
455
|
+
* return [data as Valid, null];
|
|
456
|
+
* }
|
|
457
|
+
* ```
|
|
458
|
+
*
|
|
459
|
+
* @example Batch operations
|
|
460
|
+
* ```typescript
|
|
461
|
+
* async function processAll(items: Item[]): [null, Err] | [void, null] {
|
|
462
|
+
* let errors = Err.aggregate('Batch processing failed');
|
|
463
|
+
*
|
|
464
|
+
* for (const item of items) {
|
|
465
|
+
* const [, err] = await processItem(item);
|
|
466
|
+
* if (err) {
|
|
467
|
+
* errors = errors.add(err.withMetadata({ itemId: item.id }));
|
|
468
|
+
* }
|
|
469
|
+
* }
|
|
470
|
+
*
|
|
471
|
+
* if (errors.count > 0) return [null, errors];
|
|
472
|
+
* return [undefined, null];
|
|
473
|
+
* }
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
static aggregate(message: string, errors?: Array<Err | Error | string>, options?: ErrOptions): Err;
|
|
477
|
+
/**
|
|
478
|
+
* Deserialize an Err from JSON representation.
|
|
479
|
+
*
|
|
480
|
+
* Reconstructs an Err instance from its JSON form, including
|
|
481
|
+
* cause chains and aggregated errors. Validates the input structure.
|
|
482
|
+
*
|
|
483
|
+
* @param json - JSON object matching ErrJSON structure
|
|
484
|
+
* @returns Reconstructed Err instance
|
|
485
|
+
* @throws Error if json is invalid or missing required fields
|
|
486
|
+
*
|
|
487
|
+
* @see {@link toJSON} for serializing an Err to JSON
|
|
488
|
+
*
|
|
489
|
+
* @example API response handling
|
|
490
|
+
* ```typescript
|
|
491
|
+
* const response = await fetch('/api/users/123');
|
|
492
|
+
* if (!response.ok) {
|
|
493
|
+
* const body = await response.json();
|
|
494
|
+
* if (body.error) {
|
|
495
|
+
* const err = Err.fromJSON(body.error);
|
|
496
|
+
* if (err.hasCode('NOT_FOUND')) {
|
|
497
|
+
* return showNotFound();
|
|
498
|
+
* }
|
|
499
|
+
* return showError(err);
|
|
500
|
+
* }
|
|
501
|
+
* }
|
|
502
|
+
* ```
|
|
503
|
+
*
|
|
504
|
+
* @example Message queue processing
|
|
505
|
+
* ```typescript
|
|
506
|
+
* queue.on('error', (message) => {
|
|
507
|
+
* const err = Err.fromJSON(message.payload);
|
|
508
|
+
* logger.error('Task failed', { error: err.toJSON() });
|
|
509
|
+
* });
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
static fromJSON(json: unknown): Err;
|
|
513
|
+
/**
|
|
514
|
+
* Type guard to check if a value is an Err instance.
|
|
515
|
+
*
|
|
516
|
+
* Useful for checking values from external sources where
|
|
517
|
+
* `instanceof` may not work (different realms, serialization).
|
|
518
|
+
*
|
|
519
|
+
* @param value - Any value to check
|
|
520
|
+
* @returns `true` if value is an Err instance
|
|
521
|
+
*
|
|
522
|
+
* @example Checking external/unknown values
|
|
523
|
+
* ```typescript
|
|
524
|
+
* // Useful for values from external sources
|
|
525
|
+
* function handleApiResponse(data: unknown): void {
|
|
526
|
+
* if (Err.isErr(data)) {
|
|
527
|
+
* console.error('Received error:', data.message);
|
|
528
|
+
* return;
|
|
529
|
+
* }
|
|
530
|
+
* // Process data...
|
|
531
|
+
* }
|
|
532
|
+
* ```
|
|
533
|
+
*
|
|
534
|
+
* @example With tuple pattern (preferred for known types)
|
|
535
|
+
* ```typescript
|
|
536
|
+
* function getUser(id: string): [User, null] | [null, Err] {
|
|
537
|
+
* // ...
|
|
538
|
+
* }
|
|
539
|
+
*
|
|
540
|
+
* const [user, err] = getUser('123');
|
|
541
|
+
* if (err) {
|
|
542
|
+
* console.error(err.message);
|
|
543
|
+
* return;
|
|
544
|
+
* }
|
|
545
|
+
* console.log(user.name);
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
static isErr(value: unknown): value is Err;
|
|
549
|
+
/**
|
|
550
|
+
* Wrap this error with additional context.
|
|
551
|
+
*
|
|
552
|
+
* Creates a new error that has this error as its cause. The original error
|
|
553
|
+
* is preserved and accessible via `unwrap()` or `chain()`.
|
|
554
|
+
*
|
|
555
|
+
* ## Stack Trace Behavior
|
|
556
|
+
*
|
|
557
|
+
* The new wrapper captures a fresh stack trace pointing to where `wrap()`
|
|
558
|
+
* was called. This is intentional - it shows the propagation path. The
|
|
559
|
+
* original error's stack is preserved and accessible via:
|
|
560
|
+
* - `err.unwrap()?.stack` - immediate cause's stack
|
|
561
|
+
* - `err.root.stack` - original error's stack
|
|
562
|
+
*
|
|
563
|
+
* @param context - Either a message string or full options object
|
|
564
|
+
* @returns New Err instance with this error as cause
|
|
565
|
+
*
|
|
566
|
+
* @see {@link Err.wrap} for the static version (useful in catch blocks)
|
|
567
|
+
*
|
|
568
|
+
* @example Simple wrapping
|
|
569
|
+
* ```typescript
|
|
570
|
+
* const dbErr = queryDatabase();
|
|
571
|
+
* if (Err.isErr(dbErr)) {
|
|
572
|
+
* return dbErr.wrap('Failed to fetch user');
|
|
573
|
+
* }
|
|
574
|
+
* ```
|
|
575
|
+
*
|
|
576
|
+
* @example Wrapping with full options
|
|
577
|
+
* ```typescript
|
|
578
|
+
* return originalErr.wrap({
|
|
579
|
+
* message: 'Service unavailable',
|
|
580
|
+
* code: 'SERVICE_ERROR',
|
|
581
|
+
* metadata: { service: 'user-service', retryAfter: 30 }
|
|
582
|
+
* });
|
|
583
|
+
* ```
|
|
584
|
+
*
|
|
585
|
+
* @example Accessing original stack
|
|
586
|
+
* ```typescript
|
|
587
|
+
* const wrapped = original.wrap('Context 1').wrap('Context 2');
|
|
588
|
+
* console.log(wrapped.stack); // Points to second wrap() call
|
|
589
|
+
* console.log(wrapped.root.stack); // Points to original error location
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
wrap(context: string | ErrOptions): Err;
|
|
593
|
+
/**
|
|
594
|
+
* Create a new Err with a different or added error code.
|
|
595
|
+
*
|
|
596
|
+
* Preserves the original stack trace and timestamp.
|
|
597
|
+
*
|
|
598
|
+
* @param code - The error code to set
|
|
599
|
+
* @returns New Err instance with the specified code
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```typescript
|
|
603
|
+
* const err = Err.from('Record not found').withCode('NOT_FOUND');
|
|
604
|
+
*
|
|
605
|
+
* if (err.code === 'NOT_FOUND') {
|
|
606
|
+
* return res.status(404).json(err.toJSON());
|
|
607
|
+
* }
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
withCode(code: ErrCode): Err;
|
|
611
|
+
/**
|
|
612
|
+
* Create a new Err with additional metadata.
|
|
613
|
+
*
|
|
614
|
+
* New metadata is merged with existing metadata. Preserves the original
|
|
615
|
+
* stack trace and timestamp.
|
|
616
|
+
*
|
|
617
|
+
* @param metadata - Key-value pairs to add to metadata
|
|
618
|
+
* @returns New Err instance with merged metadata
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```typescript
|
|
622
|
+
* const err = Err.from('Request failed')
|
|
623
|
+
* .withMetadata({ url: '/api/users' })
|
|
624
|
+
* .withMetadata({ statusCode: 500, retryable: true });
|
|
625
|
+
*
|
|
626
|
+
* console.log(err.metadata);
|
|
627
|
+
* // { url: '/api/users', statusCode: 500, retryable: true }
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
630
|
+
withMetadata(metadata: Record<string, unknown>): Err;
|
|
631
|
+
/**
|
|
632
|
+
* Add an error to this aggregate.
|
|
633
|
+
*
|
|
634
|
+
* Returns a new Err with the error added to the list (immutable).
|
|
635
|
+
* If this is not an aggregate error, it will be treated as one with
|
|
636
|
+
* the added error as the first child.
|
|
637
|
+
*
|
|
638
|
+
* @param error - Error to add (Err, Error, or string)
|
|
639
|
+
* @returns New Err instance with the error added
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```typescript
|
|
643
|
+
* let errors = Err.aggregate('Form validation failed');
|
|
644
|
+
*
|
|
645
|
+
* if (!email) {
|
|
646
|
+
* errors = errors.add('Email is required');
|
|
647
|
+
* }
|
|
648
|
+
* if (!password) {
|
|
649
|
+
* errors = errors.add(Err.from('Password is required').withCode('MISSING_PASSWORD'));
|
|
650
|
+
* }
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
add(error: Err | Error | string): Err;
|
|
654
|
+
/**
|
|
655
|
+
* Add multiple errors to this aggregate at once.
|
|
656
|
+
*
|
|
657
|
+
* Returns a new Err with all errors added (immutable).
|
|
658
|
+
*
|
|
659
|
+
* @param errors - Array of errors to add
|
|
660
|
+
* @returns New Err instance with all errors added
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```typescript
|
|
664
|
+
* const validationErrors = [
|
|
665
|
+
* 'Name too short',
|
|
666
|
+
* Err.from('Invalid email format').withCode('INVALID_EMAIL'),
|
|
667
|
+
* new Error('Age must be positive'),
|
|
668
|
+
* ];
|
|
669
|
+
*
|
|
670
|
+
* const aggregate = Err.aggregate('Validation failed').addAll(validationErrors);
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
addAll(errors: Array<Err | Error | string>): Err;
|
|
674
|
+
/**
|
|
675
|
+
* Whether this error is an aggregate containing multiple errors.
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* const single = Err.from('Single error');
|
|
680
|
+
* const multi = Err.aggregate('Multiple').add('One').add('Two');
|
|
681
|
+
*
|
|
682
|
+
* console.log(single.isAggregate); // false
|
|
683
|
+
* console.log(multi.isAggregate); // true
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
get isAggregate(): boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Total count of errors (including nested aggregates).
|
|
689
|
+
*
|
|
690
|
+
* For single errors, returns 1.
|
|
691
|
+
* For aggregates, recursively counts all child errors.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* const single = Err.from('One error');
|
|
696
|
+
* console.log(single.count); // 1
|
|
697
|
+
*
|
|
698
|
+
* const nested = Err.aggregate('Parent')
|
|
699
|
+
* .add('Error 1')
|
|
700
|
+
* .add(Err.aggregate('Child').add('Error 2').add('Error 3'));
|
|
701
|
+
*
|
|
702
|
+
* console.log(nested.count); // 3
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
get count(): number;
|
|
706
|
+
/**
|
|
707
|
+
* Direct child errors (for aggregates).
|
|
708
|
+
*
|
|
709
|
+
* Returns an empty array for non-aggregate errors.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* const aggregate = Err.aggregate('Batch failed')
|
|
714
|
+
* .add('Task 1 failed')
|
|
715
|
+
* .add('Task 2 failed');
|
|
716
|
+
*
|
|
717
|
+
* for (const err of aggregate.errors) {
|
|
718
|
+
* console.log(err.message);
|
|
719
|
+
* }
|
|
720
|
+
* // "Task 1 failed"
|
|
721
|
+
* // "Task 2 failed"
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
get errors(): ReadonlyArray<Err>;
|
|
725
|
+
/**
|
|
726
|
+
* The root/original error in a wrapped error chain.
|
|
727
|
+
*
|
|
728
|
+
* Follows the cause chain to find the deepest error.
|
|
729
|
+
* Returns `this` if there is no cause.
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* ```typescript
|
|
733
|
+
* const root = Err.from('Original error');
|
|
734
|
+
* const wrapped = root
|
|
735
|
+
* .wrap('Added context')
|
|
736
|
+
* .wrap('More context');
|
|
737
|
+
*
|
|
738
|
+
* console.log(wrapped.message); // "More context"
|
|
739
|
+
* console.log(wrapped.root.message); // "Original error"
|
|
740
|
+
* console.log(wrapped.root.stack); // Stack pointing to original error
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
get root(): Err;
|
|
744
|
+
/**
|
|
745
|
+
* Get the directly wrapped error (one level up).
|
|
746
|
+
*
|
|
747
|
+
* Returns `undefined` if this error has no cause.
|
|
748
|
+
*
|
|
749
|
+
* @returns The wrapped Err or undefined
|
|
750
|
+
*
|
|
751
|
+
* @example
|
|
752
|
+
* ```typescript
|
|
753
|
+
* const inner = Err.from('DB connection failed');
|
|
754
|
+
* const outer = inner.wrap('Could not save user');
|
|
755
|
+
*
|
|
756
|
+
* const unwrapped = outer.unwrap();
|
|
757
|
+
* console.log(unwrapped?.message); // "DB connection failed"
|
|
758
|
+
* console.log(inner.unwrap()); // undefined
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
unwrap(): Err | undefined;
|
|
762
|
+
/**
|
|
763
|
+
* Get the full chain of wrapped errors from root to current.
|
|
764
|
+
*
|
|
765
|
+
* The first element is the root/original error, the last is `this`.
|
|
766
|
+
*
|
|
767
|
+
* @returns Array of Err instances in causal order
|
|
768
|
+
*
|
|
769
|
+
* @remarks
|
|
770
|
+
* Time complexity: O(n) where n is the depth of the cause chain.
|
|
771
|
+
*
|
|
772
|
+
* @example
|
|
773
|
+
* ```typescript
|
|
774
|
+
* const chain = Err.from('Network timeout')
|
|
775
|
+
* .wrap('API request failed')
|
|
776
|
+
* .wrap('Could not refresh token')
|
|
777
|
+
* .wrap('Authentication failed')
|
|
778
|
+
* .chain();
|
|
779
|
+
*
|
|
780
|
+
* console.log(chain.map(e => e.message));
|
|
781
|
+
* // [
|
|
782
|
+
* // "Network timeout",
|
|
783
|
+
* // "API request failed",
|
|
784
|
+
* // "Could not refresh token",
|
|
785
|
+
* // "Authentication failed"
|
|
786
|
+
* // ]
|
|
787
|
+
* ```
|
|
788
|
+
*/
|
|
789
|
+
chain(): Err[];
|
|
790
|
+
/**
|
|
791
|
+
* Flatten all errors into a single array.
|
|
792
|
+
*
|
|
793
|
+
* For aggregates, recursively collects all leaf errors.
|
|
794
|
+
* For single errors, returns an array containing just this error.
|
|
795
|
+
*
|
|
796
|
+
* @returns Flattened array of all individual errors
|
|
797
|
+
*
|
|
798
|
+
* @remarks
|
|
799
|
+
* Time complexity: O(n) where n is the total number of errors in all nested aggregates.
|
|
800
|
+
* Recursively traverses the error tree.
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* ```typescript
|
|
804
|
+
* const nested = Err.aggregate('All errors')
|
|
805
|
+
* .add('Error A')
|
|
806
|
+
* .add(Err.aggregate('Group B')
|
|
807
|
+
* .add('Error B1')
|
|
808
|
+
* .add('Error B2'))
|
|
809
|
+
* .add('Error C');
|
|
810
|
+
*
|
|
811
|
+
* const flat = nested.flatten();
|
|
812
|
+
* console.log(flat.map(e => e.message));
|
|
813
|
+
* // ["Error A", "Error B1", "Error B2", "Error C"]
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
flatten(): Err[];
|
|
817
|
+
/**
|
|
818
|
+
* Check if this error or any error in its chain/aggregate has a specific code.
|
|
819
|
+
*
|
|
820
|
+
* Searches the cause chain and all aggregated errors.
|
|
821
|
+
*
|
|
822
|
+
* @param code - The error code to search for
|
|
823
|
+
* @returns `true` if the code is found anywhere in the error tree
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* ```typescript
|
|
827
|
+
* const err = Err.from('DB error', 'DB_ERROR')
|
|
828
|
+
* .wrap('Repository failed')
|
|
829
|
+
* .wrap('Service unavailable');
|
|
830
|
+
*
|
|
831
|
+
* console.log(err.hasCode('DB_ERROR')); // true
|
|
832
|
+
* console.log(err.hasCode('NETWORK_ERROR')); // false
|
|
833
|
+
* ```
|
|
834
|
+
*/
|
|
835
|
+
hasCode(code: ErrCode): boolean;
|
|
836
|
+
/**
|
|
837
|
+
* Check if this error or any error in its chain/aggregate has a code
|
|
838
|
+
* matching the given prefix with boundary awareness.
|
|
839
|
+
*
|
|
840
|
+
* This enables hierarchical error code patterns like `AUTH:TOKEN:EXPIRED`
|
|
841
|
+
* where libraries define base codes and consumers extend with subcodes.
|
|
842
|
+
*
|
|
843
|
+
* Matches if:
|
|
844
|
+
* - Code equals prefix exactly (e.g., `"AUTH"` matches `"AUTH"`)
|
|
845
|
+
* - Code starts with prefix + boundary (e.g., `"AUTH"` matches `"AUTH:EXPIRED"`)
|
|
846
|
+
*
|
|
847
|
+
* Does NOT match partial strings (e.g., `"AUTH"` does NOT match `"AUTHORIZATION"`).
|
|
848
|
+
*
|
|
849
|
+
* @param prefix - The code prefix to search for
|
|
850
|
+
* @param boundary - Separator character/string between code segments (default: ":")
|
|
851
|
+
* @returns `true` if a matching code is found anywhere in the error tree
|
|
852
|
+
*
|
|
853
|
+
* @example Basic hierarchical codes
|
|
854
|
+
* ```typescript
|
|
855
|
+
* const err = Err.from('Token expired', { code: 'AUTH:TOKEN:EXPIRED' });
|
|
856
|
+
*
|
|
857
|
+
* err.hasCodePrefix('AUTH'); // true (matches AUTH:*)
|
|
858
|
+
* err.hasCodePrefix('AUTH:TOKEN'); // true (matches AUTH:TOKEN:*)
|
|
859
|
+
* err.hasCodePrefix('AUTHORIZATION'); // false (no boundary match)
|
|
860
|
+
* ```
|
|
861
|
+
*
|
|
862
|
+
* @example Custom boundary
|
|
863
|
+
* ```typescript
|
|
864
|
+
* const err = Err.from('Not found', { code: 'HTTP.404.NOT_FOUND' });
|
|
865
|
+
*
|
|
866
|
+
* err.hasCodePrefix('HTTP', '.'); // true
|
|
867
|
+
* err.hasCodePrefix('HTTP.404', '.'); // true
|
|
868
|
+
* err.hasCodePrefix('HTTP', ':'); // false (wrong boundary)
|
|
869
|
+
* ```
|
|
870
|
+
*
|
|
871
|
+
* @example Search in error tree
|
|
872
|
+
* ```typescript
|
|
873
|
+
* const err = Err.from('DB error', { code: 'DB:CONNECTION' })
|
|
874
|
+
* .wrap('Service failed', { code: 'SERVICE:UNAVAILABLE' });
|
|
875
|
+
*
|
|
876
|
+
* err.hasCodePrefix('DB'); // true (found in cause)
|
|
877
|
+
* err.hasCodePrefix('SERVICE'); // true (found in current)
|
|
878
|
+
* ```
|
|
879
|
+
*/
|
|
880
|
+
hasCodePrefix(prefix: string, boundary?: string): boolean;
|
|
881
|
+
/**
|
|
882
|
+
* Find the first error matching a predicate.
|
|
883
|
+
*
|
|
884
|
+
* Searches this error, its cause chain, and all aggregated errors.
|
|
885
|
+
*
|
|
886
|
+
* @param predicate - Function to test each error
|
|
887
|
+
* @returns The first matching Err or undefined
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* const err = Err.aggregate('Multiple failures')
|
|
892
|
+
* .add(Err.from('Not found', 'NOT_FOUND'))
|
|
893
|
+
* .add(Err.from('Timeout', 'TIMEOUT'));
|
|
894
|
+
*
|
|
895
|
+
* const timeout = err.find(e => e.code === 'TIMEOUT');
|
|
896
|
+
* console.log(timeout?.message); // "Timeout"
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
find(predicate: (e: Err) => boolean): Err | undefined;
|
|
900
|
+
/**
|
|
901
|
+
* Find all errors matching a predicate.
|
|
902
|
+
*
|
|
903
|
+
* Searches this error, its cause chain, and all aggregated errors.
|
|
904
|
+
*
|
|
905
|
+
* @param predicate - Function to test each error
|
|
906
|
+
* @returns Array of all matching Err instances
|
|
907
|
+
*
|
|
908
|
+
* @example
|
|
909
|
+
* ```typescript
|
|
910
|
+
* const err = Err.aggregate('Validation failed')
|
|
911
|
+
* .add(Err.from('Name required', 'REQUIRED'))
|
|
912
|
+
* .add(Err.from('Invalid email', 'INVALID'))
|
|
913
|
+
* .add(Err.from('Age required', 'REQUIRED'));
|
|
914
|
+
*
|
|
915
|
+
* const required = err.filter(e => e.code === 'REQUIRED');
|
|
916
|
+
* console.log(required.length); // 2
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
919
|
+
filter(predicate: (e: Err) => boolean): Err[];
|
|
920
|
+
/**
|
|
921
|
+
* Convert to a JSON-serializable object.
|
|
922
|
+
*
|
|
923
|
+
* Useful for logging, API responses, and serialization.
|
|
924
|
+
* Use options to control what's included (e.g., omit stack for public APIs).
|
|
925
|
+
*
|
|
926
|
+
* @param options - Control what fields are included
|
|
927
|
+
* @returns Plain object representation
|
|
928
|
+
*
|
|
929
|
+
* @see {@link fromJSON} for deserializing an Err from JSON
|
|
930
|
+
*
|
|
931
|
+
* @example Full serialization (default)
|
|
932
|
+
* ```typescript
|
|
933
|
+
* const err = Err.from('Not found', {
|
|
934
|
+
* code: 'NOT_FOUND',
|
|
935
|
+
* metadata: { userId: '123' }
|
|
936
|
+
* });
|
|
937
|
+
*
|
|
938
|
+
* console.log(JSON.stringify(err.toJSON(), null, 2));
|
|
939
|
+
* // {
|
|
940
|
+
* // "message": "Not found",
|
|
941
|
+
* // "code": "NOT_FOUND",
|
|
942
|
+
* // "metadata": { "userId": "123" },
|
|
943
|
+
* // "timestamp": "2024-01-15T10:30:00.000Z",
|
|
944
|
+
* // "stack": "Error: ...",
|
|
945
|
+
* // "errors": []
|
|
946
|
+
* // }
|
|
947
|
+
* ```
|
|
948
|
+
*
|
|
949
|
+
* @example Public API response (no stack)
|
|
950
|
+
* ```typescript
|
|
951
|
+
* app.get('/user/:id', (req, res) => {
|
|
952
|
+
* const result = getUser(req.params.id);
|
|
953
|
+
* if (Err.isErr(result)) {
|
|
954
|
+
* const status = result.code === 'NOT_FOUND' ? 404 : 500;
|
|
955
|
+
* return res.status(status).json({
|
|
956
|
+
* error: result.toJSON({ stack: false })
|
|
957
|
+
* });
|
|
958
|
+
* }
|
|
959
|
+
* res.json(result);
|
|
960
|
+
* });
|
|
961
|
+
* ```
|
|
962
|
+
*
|
|
963
|
+
* @example Minimal payload
|
|
964
|
+
* ```typescript
|
|
965
|
+
* err.toJSON({ stack: false, metadata: false });
|
|
966
|
+
* // Only includes: message, code, timestamp, cause, errors
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
969
|
+
toJSON(options?: ErrJSONOptions): ErrJSON;
|
|
970
|
+
/**
|
|
971
|
+
* Recursive code search helper.
|
|
972
|
+
* @param matcher
|
|
973
|
+
* @private
|
|
974
|
+
*/
|
|
975
|
+
private _searchCode;
|
|
976
|
+
/**
|
|
977
|
+
* Pattern to identify internal Err class frames to filter out.
|
|
978
|
+
* Matches frames from err.ts file (handles both "at Err.from" and "at from" patterns).
|
|
979
|
+
* @internal
|
|
980
|
+
*/
|
|
981
|
+
private static readonly INTERNAL_FRAME_PATTERN;
|
|
982
|
+
/**
|
|
983
|
+
* Filter out internal Err class frames from stack trace.
|
|
984
|
+
* This makes stack traces more useful by starting at user code.
|
|
985
|
+
*
|
|
986
|
+
* @param stack - Raw stack trace string
|
|
987
|
+
* @returns Stack with internal frames removed
|
|
988
|
+
* @internal
|
|
989
|
+
*/
|
|
990
|
+
private static _filterInternalFrames;
|
|
991
|
+
/**
|
|
992
|
+
* Parse and extract stack frames from the stack trace.
|
|
993
|
+
*
|
|
994
|
+
* @param limit - Maximum number of frames to return (undefined = all)
|
|
995
|
+
* @returns Array of stack frame strings
|
|
996
|
+
* @internal
|
|
997
|
+
*/
|
|
998
|
+
private _getStackFrames;
|
|
999
|
+
/**
|
|
1000
|
+
* Count remaining causes in the chain from a given error.
|
|
1001
|
+
*
|
|
1002
|
+
* @param err - Starting error
|
|
1003
|
+
* @returns Number of causes remaining
|
|
1004
|
+
* @internal
|
|
1005
|
+
*/
|
|
1006
|
+
private _countRemainingCauses;
|
|
1007
|
+
/**
|
|
1008
|
+
* Convert to a formatted string for logging/display.
|
|
1009
|
+
*
|
|
1010
|
+
* Includes cause chain and aggregated errors with indentation.
|
|
1011
|
+
* When called with options, can include additional details like
|
|
1012
|
+
* stack traces, timestamps, and metadata.
|
|
1013
|
+
*
|
|
1014
|
+
* @param options - Formatting options (optional)
|
|
1015
|
+
* @returns Formatted error string
|
|
1016
|
+
*
|
|
1017
|
+
* @example Basic usage (no options - backward compatible)
|
|
1018
|
+
* ```typescript
|
|
1019
|
+
* const err = Err.from('DB error')
|
|
1020
|
+
* .wrap('Repository failed')
|
|
1021
|
+
* .wrap('Service unavailable');
|
|
1022
|
+
*
|
|
1023
|
+
* console.log(err.toString());
|
|
1024
|
+
* // [ERROR] Service unavailable
|
|
1025
|
+
* // Caused by: [ERROR] Repository failed
|
|
1026
|
+
* // Caused by: [ERROR] DB error
|
|
1027
|
+
* ```
|
|
1028
|
+
*
|
|
1029
|
+
* @example With options
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* const err = Err.from('Connection failed', {
|
|
1032
|
+
* code: 'DB:CONNECTION',
|
|
1033
|
+
* metadata: { host: 'localhost', port: 5432 }
|
|
1034
|
+
* });
|
|
1035
|
+
*
|
|
1036
|
+
* console.log(err.toString({ date: true, metadata: true, stack: 3 }));
|
|
1037
|
+
* // [2024-01-15T10:30:00.000Z] [DB:CONNECTION] Connection failed
|
|
1038
|
+
* // metadata: {"host":"localhost","port":5432}
|
|
1039
|
+
* // stack:
|
|
1040
|
+
* // at Database.connect (src/db.ts:45)
|
|
1041
|
+
* // at Repository.init (src/repo.ts:23)
|
|
1042
|
+
* // at Service.start (src/service.ts:12)
|
|
1043
|
+
* ```
|
|
1044
|
+
*
|
|
1045
|
+
* @example Aggregate
|
|
1046
|
+
* ```typescript
|
|
1047
|
+
* const err = Err.aggregate('Validation failed', [], { code: 'VALIDATION' })
|
|
1048
|
+
* .add('Name required')
|
|
1049
|
+
* .add('Email invalid');
|
|
1050
|
+
*
|
|
1051
|
+
* console.log(err.toString());
|
|
1052
|
+
* // [VALIDATION] Validation failed
|
|
1053
|
+
* // Errors (2):
|
|
1054
|
+
* // - [ERROR] Name required
|
|
1055
|
+
* // - [ERROR] Email invalid
|
|
1056
|
+
* ```
|
|
1057
|
+
*
|
|
1058
|
+
* @example With maxDepth limit
|
|
1059
|
+
* ```typescript
|
|
1060
|
+
* const deep = Err.from('Root')
|
|
1061
|
+
* .wrap('Level 1')
|
|
1062
|
+
* .wrap('Level 2')
|
|
1063
|
+
* .wrap('Level 3');
|
|
1064
|
+
*
|
|
1065
|
+
* console.log(deep.toString({ maxDepth: 2 }));
|
|
1066
|
+
* // [ERROR] Level 3
|
|
1067
|
+
* // Caused by: [ERROR] Level 2
|
|
1068
|
+
* // ... (2 more causes)
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
1071
|
+
toString(options?: ToStringOptions): string;
|
|
1072
|
+
/**
|
|
1073
|
+
* Internal toString implementation with depth tracking.
|
|
1074
|
+
* @internal
|
|
1075
|
+
*/
|
|
1076
|
+
private _toStringInternal;
|
|
1077
|
+
/**
|
|
1078
|
+
* Convert to a native Error for interop with throw-based APIs.
|
|
1079
|
+
*
|
|
1080
|
+
* Creates an Error with:
|
|
1081
|
+
* - `message`: This error's message
|
|
1082
|
+
* - `name`: This error's code (or "Err")
|
|
1083
|
+
* - `stack`: This error's original stack trace
|
|
1084
|
+
* - `cause`: Converted cause chain (native Error)
|
|
1085
|
+
*
|
|
1086
|
+
* Note: Metadata is not included on the native Error.
|
|
1087
|
+
*
|
|
1088
|
+
* @returns Native Error instance
|
|
1089
|
+
*
|
|
1090
|
+
* @example
|
|
1091
|
+
* ```typescript
|
|
1092
|
+
* const err = Err.from('Something failed', 'MY_ERROR');
|
|
1093
|
+
*
|
|
1094
|
+
* // If you need to throw for some API
|
|
1095
|
+
* throw err.toError();
|
|
1096
|
+
*
|
|
1097
|
+
* // The thrown error will have:
|
|
1098
|
+
* // - error.message === "Something failed"
|
|
1099
|
+
* // - error.name === "MY_ERROR"
|
|
1100
|
+
* // - error.stack === (original stack trace)
|
|
1101
|
+
* // - error.cause === (if wrapped)
|
|
1102
|
+
* ```
|
|
1103
|
+
*/
|
|
1104
|
+
toError(): Error;
|
|
1105
|
+
/**
|
|
1106
|
+
* Get the captured stack trace.
|
|
1107
|
+
*
|
|
1108
|
+
* For errors created from native Errors, this is the original stack.
|
|
1109
|
+
* For errors created via `Err.from(string)`, this is the stack at creation.
|
|
1110
|
+
* For wrapped errors, use `.root.stack` to get the original location.
|
|
1111
|
+
*
|
|
1112
|
+
* @returns Stack trace string or undefined
|
|
1113
|
+
*/
|
|
1114
|
+
get stack(): string | undefined;
|
|
1115
|
+
}
|
|
1116
|
+
//# sourceMappingURL=err.d.ts.map
|