wellcrafted 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -81
- package/dist/error/index.d.ts +56 -120
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +41 -65
- package/dist/error/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ try {
|
|
|
21
21
|
const { data, error } = await saveUser(user);
|
|
22
22
|
if (error) {
|
|
23
23
|
switch (error.name) {
|
|
24
|
-
case "ValidationError":
|
|
25
|
-
showToast(`Invalid ${error.
|
|
24
|
+
case "ValidationError":
|
|
25
|
+
showToast(`Invalid ${error.field}`);
|
|
26
26
|
break;
|
|
27
27
|
case "AuthError":
|
|
28
28
|
redirectToLogin();
|
|
@@ -54,18 +54,22 @@ function getUser(id: UserId) { /* ... */ }
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
### 📋 Tagged Errors
|
|
57
|
-
Structured, serializable errors with a
|
|
57
|
+
Structured, serializable errors with a declarative API
|
|
58
58
|
```typescript
|
|
59
|
-
import {
|
|
59
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
ValidationError
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
const errors = defineErrors({
|
|
62
|
+
// Static error — no fields needed
|
|
63
|
+
ValidationError: () => ({
|
|
64
|
+
message: "Email is required",
|
|
65
|
+
}),
|
|
66
|
+
// Structured error — fields are spread flat on the error object
|
|
67
|
+
ApiError: (fields: { endpoint: string }) => ({
|
|
68
|
+
...fields,
|
|
69
|
+
message: `Request to ${fields.endpoint} failed`,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
const { ValidationError, ApiError } = errors;
|
|
69
73
|
```
|
|
70
74
|
|
|
71
75
|
### 🔄 Query Integration
|
|
@@ -108,22 +112,22 @@ npm install wellcrafted
|
|
|
108
112
|
|
|
109
113
|
```typescript
|
|
110
114
|
import { tryAsync } from "wellcrafted/result";
|
|
111
|
-
import {
|
|
115
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
112
116
|
|
|
113
|
-
// Define your
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
// Define your errors declaratively
|
|
118
|
+
const errors = defineErrors({
|
|
119
|
+
ApiError: (fields: { endpoint: string }) => ({
|
|
120
|
+
...fields,
|
|
121
|
+
message: `Failed to fetch ${fields.endpoint}`,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
const { ApiError, ApiErr } = errors;
|
|
125
|
+
type ApiError = InferError<typeof errors, "ApiError">;
|
|
118
126
|
|
|
119
127
|
// Wrap any throwing operation
|
|
120
128
|
const { data, error } = await tryAsync({
|
|
121
129
|
try: () => fetch('/api/user').then(r => r.json()),
|
|
122
|
-
catch: (e) => ApiErr({
|
|
123
|
-
message: "Failed to fetch user",
|
|
124
|
-
context: { endpoint: '/api/user' },
|
|
125
|
-
cause: { name: "FetchError", message: String(e) }
|
|
126
|
-
})
|
|
130
|
+
catch: (e) => ApiErr({ endpoint: '/api/user' })
|
|
127
131
|
});
|
|
128
132
|
|
|
129
133
|
if (error) {
|
|
@@ -216,29 +220,31 @@ if (error) {
|
|
|
216
220
|
### Wrap Unsafe Operations
|
|
217
221
|
|
|
218
222
|
```typescript
|
|
219
|
-
|
|
220
|
-
const { ParseError, ParseErr } = createTaggedError("ParseError")
|
|
221
|
-
.withContext<{ input: string }>();
|
|
223
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
222
224
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
// Define errors declaratively
|
|
226
|
+
const errors = defineErrors({
|
|
227
|
+
ParseError: (fields: { input: string }) => ({
|
|
228
|
+
...fields,
|
|
229
|
+
message: `Invalid JSON: ${fields.input.slice(0, 50)}`,
|
|
230
|
+
}),
|
|
231
|
+
NetworkError: (fields: { url: string }) => ({
|
|
232
|
+
...fields,
|
|
233
|
+
message: `Request to ${fields.url} failed`,
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
const { ParseErr, NetworkErr } = errors;
|
|
225
237
|
|
|
226
238
|
// Synchronous
|
|
227
239
|
const result = trySync({
|
|
228
240
|
try: () => JSON.parse(jsonString),
|
|
229
|
-
catch: () => ParseErr({
|
|
230
|
-
message: "Invalid JSON",
|
|
231
|
-
context: { input: jsonString }
|
|
232
|
-
})
|
|
241
|
+
catch: () => ParseErr({ input: jsonString })
|
|
233
242
|
});
|
|
234
243
|
|
|
235
244
|
// Asynchronous
|
|
236
245
|
const result = await tryAsync({
|
|
237
246
|
try: () => fetch(url),
|
|
238
|
-
catch: () => NetworkErr({
|
|
239
|
-
message: "Request failed",
|
|
240
|
-
context: { url }
|
|
241
|
-
})
|
|
247
|
+
catch: () => NetworkErr({ url })
|
|
242
248
|
});
|
|
243
249
|
```
|
|
244
250
|
|
|
@@ -246,12 +252,19 @@ const result = await tryAsync({
|
|
|
246
252
|
|
|
247
253
|
```typescript
|
|
248
254
|
// 1. Service Layer - Pure business logic
|
|
249
|
-
import {
|
|
255
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
250
256
|
import { tryAsync, Result, Ok } from "wellcrafted/result";
|
|
251
257
|
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
const recorderErrors = defineErrors({
|
|
259
|
+
RecorderServiceError: (fields: { currentState?: string; permissions?: string }) => ({
|
|
260
|
+
...fields,
|
|
261
|
+
message: fields.permissions
|
|
262
|
+
? `Missing ${fields.permissions} permission`
|
|
263
|
+
: `Invalid recorder state: ${fields.currentState}`,
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
|
|
267
|
+
type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;
|
|
255
268
|
|
|
256
269
|
export function createRecorderService() {
|
|
257
270
|
let isRecording = false;
|
|
@@ -260,10 +273,7 @@ export function createRecorderService() {
|
|
|
260
273
|
return {
|
|
261
274
|
async startRecording(): Promise<Result<void, RecorderServiceError>> {
|
|
262
275
|
if (isRecording) {
|
|
263
|
-
return RecorderServiceErr({
|
|
264
|
-
message: "Already recording",
|
|
265
|
-
context: { currentState: 'recording' }
|
|
266
|
-
});
|
|
276
|
+
return RecorderServiceErr({ currentState: 'recording' });
|
|
267
277
|
}
|
|
268
278
|
|
|
269
279
|
return tryAsync({
|
|
@@ -273,19 +283,13 @@ export function createRecorderService() {
|
|
|
273
283
|
// ... recording setup
|
|
274
284
|
isRecording = true;
|
|
275
285
|
},
|
|
276
|
-
catch: () => RecorderServiceErr({
|
|
277
|
-
message: "Failed to start recording",
|
|
278
|
-
context: { permissions: 'microphone' }
|
|
279
|
-
})
|
|
286
|
+
catch: () => RecorderServiceErr({ permissions: 'microphone' })
|
|
280
287
|
});
|
|
281
288
|
},
|
|
282
289
|
|
|
283
290
|
async stopRecording(): Promise<Result<Blob, RecorderServiceError>> {
|
|
284
291
|
if (!isRecording) {
|
|
285
|
-
return RecorderServiceErr({
|
|
286
|
-
message: "Not currently recording",
|
|
287
|
-
context: { currentState: 'idle' }
|
|
288
|
-
});
|
|
292
|
+
return RecorderServiceErr({ currentState: 'idle' });
|
|
289
293
|
}
|
|
290
294
|
|
|
291
295
|
// Stop recording and return blob...
|
|
@@ -379,12 +383,17 @@ const { data: parsed } = trySync({
|
|
|
379
383
|
|
|
380
384
|
### Propagation Pattern (May Fail)
|
|
381
385
|
```typescript
|
|
382
|
-
const
|
|
386
|
+
const parseErrors = defineErrors({
|
|
387
|
+
ParseError: () => ({
|
|
388
|
+
message: "Invalid JSON",
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
391
|
+
const { ParseErr } = parseErrors;
|
|
383
392
|
|
|
384
393
|
// When catch can return Err<E>, function returns Result<T, E>
|
|
385
394
|
const mayFail = trySync({
|
|
386
395
|
try: () => JSON.parse(riskyJson),
|
|
387
|
-
catch: () => ParseErr(
|
|
396
|
+
catch: () => ParseErr()
|
|
388
397
|
});
|
|
389
398
|
// mayFail: Result<object, ParseError> - Must check for errors
|
|
390
399
|
if (isOk(mayFail)) {
|
|
@@ -402,7 +411,7 @@ const smartParse = trySync({
|
|
|
402
411
|
return Ok({}); // Return Ok<T> for fallback
|
|
403
412
|
}
|
|
404
413
|
// Propagate other errors
|
|
405
|
-
return ParseErr(
|
|
414
|
+
return ParseErr();
|
|
406
415
|
}
|
|
407
416
|
});
|
|
408
417
|
// smartParse: Result<object, ParseError> - Mixed handling = Result type
|
|
@@ -432,13 +441,18 @@ Based on real-world usage, here's the recommended pattern for creating services
|
|
|
432
441
|
### Factory Function Pattern
|
|
433
442
|
|
|
434
443
|
```typescript
|
|
435
|
-
import {
|
|
444
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
436
445
|
import { Result, Ok } from "wellcrafted/result";
|
|
437
446
|
|
|
438
|
-
// 1. Define service-specific errors with typed
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
447
|
+
// 1. Define service-specific errors with typed fields and message
|
|
448
|
+
const recorderErrors = defineErrors({
|
|
449
|
+
RecorderServiceError: (fields: { isRecording: boolean }) => ({
|
|
450
|
+
...fields,
|
|
451
|
+
message: fields.isRecording ? "Already recording" : "Not currently recording",
|
|
452
|
+
}),
|
|
453
|
+
});
|
|
454
|
+
const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
|
|
455
|
+
type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;
|
|
442
456
|
|
|
443
457
|
// 2. Create service with factory function
|
|
444
458
|
export function createRecorderService() {
|
|
@@ -449,10 +463,7 @@ export function createRecorderService() {
|
|
|
449
463
|
return {
|
|
450
464
|
startRecording(): Result<void, RecorderServiceError> {
|
|
451
465
|
if (isRecording) {
|
|
452
|
-
return RecorderServiceErr({
|
|
453
|
-
message: "Already recording",
|
|
454
|
-
context: { isRecording }
|
|
455
|
-
});
|
|
466
|
+
return RecorderServiceErr({ isRecording });
|
|
456
467
|
}
|
|
457
468
|
|
|
458
469
|
isRecording = true;
|
|
@@ -461,10 +472,7 @@ export function createRecorderService() {
|
|
|
461
472
|
|
|
462
473
|
stopRecording(): Result<Blob, RecorderServiceError> {
|
|
463
474
|
if (!isRecording) {
|
|
464
|
-
return RecorderServiceErr({
|
|
465
|
-
message: "Not currently recording",
|
|
466
|
-
context: { isRecording }
|
|
467
|
-
});
|
|
475
|
+
return RecorderServiceErr({ isRecording });
|
|
468
476
|
}
|
|
469
477
|
|
|
470
478
|
isRecording = false;
|
|
@@ -548,8 +556,16 @@ export async function GET(request: Request) {
|
|
|
548
556
|
<summary><b>Form Validation</b></summary>
|
|
549
557
|
|
|
550
558
|
```typescript
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
import { defineErrors, type InferError } from "wellcrafted/error";
|
|
560
|
+
|
|
561
|
+
const formErrors = defineErrors({
|
|
562
|
+
FormError: (fields: { fields: Record<string, string[]> }) => ({
|
|
563
|
+
...fields,
|
|
564
|
+
message: `Validation failed for: ${Object.keys(fields.fields).join(", ")}`,
|
|
565
|
+
}),
|
|
566
|
+
});
|
|
567
|
+
const { FormErr } = formErrors;
|
|
568
|
+
type FormError = InferError<typeof formErrors, "FormError">;
|
|
553
569
|
|
|
554
570
|
function validateLoginForm(data: unknown): Result<LoginData, FormError> {
|
|
555
571
|
const errors: Record<string, string[]> = {};
|
|
@@ -559,10 +575,7 @@ function validateLoginForm(data: unknown): Result<LoginData, FormError> {
|
|
|
559
575
|
}
|
|
560
576
|
|
|
561
577
|
if (Object.keys(errors).length > 0) {
|
|
562
|
-
return FormErr({
|
|
563
|
-
message: "Validation failed",
|
|
564
|
-
context: { fields: errors }
|
|
565
|
-
});
|
|
578
|
+
return FormErr({ fields: errors });
|
|
566
579
|
}
|
|
567
580
|
|
|
568
581
|
return Ok(data as LoginData);
|
|
@@ -630,19 +643,19 @@ For comprehensive examples, service layer patterns, framework integrations, and
|
|
|
630
643
|
- **`defineMutation(options)`** - Define a mutation with dual interface (`.options` + callable/`.execute()`)
|
|
631
644
|
|
|
632
645
|
### Error Functions
|
|
633
|
-
- **`
|
|
634
|
-
-
|
|
635
|
-
-
|
|
636
|
-
-
|
|
637
|
-
-
|
|
638
|
-
-
|
|
646
|
+
- **`defineErrors(definitions)`** - Define multiple error factories in a single declaration
|
|
647
|
+
- Each key becomes an error name; the value is a factory function returning fields + `message`
|
|
648
|
+
- Returns `{ErrorName}` (plain error) and `{ErrorName}Err` (Err-wrapped) for each key
|
|
649
|
+
- Factory functions receive typed fields and return `{ ...fields, message }`
|
|
650
|
+
- No-field errors use `() => ({ message: '...' })`
|
|
651
|
+
- `name` is a reserved key — prevented at compile time
|
|
639
652
|
- **`extractErrorMessage(error)`** - Extract readable message from unknown error
|
|
640
653
|
|
|
641
654
|
### Types
|
|
642
655
|
- **`Result<T, E>`** - Union of Ok<T> | Err<E>
|
|
643
656
|
- **`Ok<T>`** - Success result type
|
|
644
657
|
- **`Err<E>`** - Error result type
|
|
645
|
-
- **`
|
|
658
|
+
- **`InferError<TErrors, TName>`** - Extract error type from `defineErrors` result
|
|
646
659
|
- **`Brand<T, B>`** - Branded type wrapper
|
|
647
660
|
- **`ExtractOkFromResult<R>`** - Extract Ok variant from Result union
|
|
648
661
|
- **`ExtractErrFromResult<R>`** - Extract Err variant from Result union
|
package/dist/error/index.d.ts
CHANGED
|
@@ -14,48 +14,74 @@ type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
|
14
14
|
*/
|
|
15
15
|
type JsonObject = Record<string, JsonValue>;
|
|
16
16
|
/**
|
|
17
|
-
* Base type for any tagged error, used as a constraint
|
|
17
|
+
* Base type for any tagged error, used as a minimum constraint.
|
|
18
18
|
*/
|
|
19
19
|
type AnyTaggedError = {
|
|
20
20
|
name: string;
|
|
21
21
|
message: string;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* - When TContext is a specific type without undefined: context is REQUIRED with that exact type
|
|
24
|
+
* Constructor return must include `message: string`.
|
|
25
|
+
* JSON serializability is a convention, not enforced at the type level
|
|
26
|
+
* (optional fields produce `T | undefined` which breaks `JsonObject`).
|
|
28
27
|
*/
|
|
29
|
-
type
|
|
30
|
-
|
|
31
|
-
} : {
|
|
32
|
-
context: TContext;
|
|
28
|
+
type ErrorBody = {
|
|
29
|
+
message: string;
|
|
33
30
|
};
|
|
34
31
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* - When TCause includes undefined (e.g., `NetworkError | undefined`): cause is OPTIONAL, constrained
|
|
38
|
-
* - When TCause is a specific type without undefined: cause is REQUIRED
|
|
32
|
+
* Per-key validation: tells the user exactly what `name` will be stamped as.
|
|
33
|
+
* If a user provides `name` in the return object, they see a descriptive error.
|
|
39
34
|
*/
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
cause: TCause;
|
|
35
|
+
type ValidateErrorBody<K extends string> = {
|
|
36
|
+
message: string;
|
|
37
|
+
name?: `The 'name' key is reserved as '${K}'. Remove it.`;
|
|
44
38
|
};
|
|
39
|
+
/** The config: each key is a variant name, each value is a constructor function. */
|
|
40
|
+
type ErrorsConfig = Record<string, (...args: any[]) => ErrorBody>;
|
|
41
|
+
/** Validates each config entry, injecting the key-specific `name` reservation message. */
|
|
42
|
+
type ValidatedConfig<T extends ErrorsConfig> = { [K in keyof T & string]: T[K] extends ((...args: infer A) => infer R) ? (...args: A) => R & ValidateErrorBody<K> : T[K] };
|
|
43
|
+
/** Single factory: takes constructor args, returns Err-wrapped error. */
|
|
44
|
+
type ErrorFactory<TName extends string, TFn extends (...args: any[]) => ErrorBody> = { [K in TName]: (...args: Parameters<TFn>) => Err<Readonly<{
|
|
45
|
+
name: TName;
|
|
46
|
+
} & ReturnType<TFn>>> };
|
|
47
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
48
|
+
/** Return type of `defineErrors`. Maps each config key to its factory. */
|
|
49
|
+
type DefineErrorsReturn<TConfig extends ErrorsConfig> = UnionToIntersection<{ [K in keyof TConfig & string]: ErrorFactory<K, TConfig[K]> }[keyof TConfig & string]>;
|
|
50
|
+
/** Extract the error type from a single factory. */
|
|
51
|
+
type InferError<T> = T extends ((...args: any[]) => Err<infer R>) ? R : never;
|
|
52
|
+
/** Extract union of ALL error types from a defineErrors return. */
|
|
53
|
+
type InferErrors<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => Err<infer R>) ? R : never }[keyof T];
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/error/defineErrors.d.ts
|
|
45
56
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
57
|
+
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
58
|
+
*
|
|
59
|
+
* Each key is a short variant name (the namespace provides context). Every
|
|
60
|
+
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
61
|
+
* handlers. The variant name is stamped as `name` on the error object.
|
|
48
62
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const HttpError = defineErrors({
|
|
66
|
+
* Connection: ({ cause }: { cause: string }) => ({
|
|
67
|
+
* message: `Failed to connect: ${cause}`,
|
|
68
|
+
* cause,
|
|
69
|
+
* }),
|
|
70
|
+
* Parse: ({ cause }: { cause: string }) => ({
|
|
71
|
+
* message: `Failed to parse: ${cause}`,
|
|
72
|
+
* cause,
|
|
73
|
+
* }),
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* type HttpError = InferErrors<typeof HttpError>;
|
|
77
|
+
*
|
|
78
|
+
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
79
|
+
* ```
|
|
52
80
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
message: string;
|
|
56
|
-
} & WithContext<TContext> & WithCause<TCause>>;
|
|
81
|
+
declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
|
|
82
|
+
//# sourceMappingURL=defineErrors.d.ts.map
|
|
57
83
|
//#endregion
|
|
58
|
-
//#region src/error/
|
|
84
|
+
//#region src/error/extractErrorMessage.d.ts
|
|
59
85
|
/**
|
|
60
86
|
* Extracts a readable error message from an unknown error value
|
|
61
87
|
*
|
|
@@ -63,98 +89,8 @@ type TaggedError<TName extends string = string, TContext extends JsonObject | un
|
|
|
63
89
|
* @returns A string representation of the error
|
|
64
90
|
*/
|
|
65
91
|
declare function extractErrorMessage(error: unknown): string;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*/
|
|
69
|
-
type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
|
|
70
|
-
/**
|
|
71
|
-
* Input provided to the message template function.
|
|
72
|
-
* Contains everything the error will have except `message` (since that's what it computes).
|
|
73
|
-
*/
|
|
74
|
-
type MessageInput<TName extends string, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = {
|
|
75
|
-
name: TName;
|
|
76
|
-
} & ([TContext] extends [undefined] ? Record<never, never> : {
|
|
77
|
-
context: Exclude<TContext, undefined>;
|
|
78
|
-
}) & ([TCause] extends [undefined] ? Record<never, never> : {
|
|
79
|
-
cause: Exclude<TCause, undefined>;
|
|
80
|
-
});
|
|
81
|
-
/**
|
|
82
|
-
* Message template function type.
|
|
83
|
-
*/
|
|
84
|
-
type MessageFn<TName extends string, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = (input: MessageInput<TName, TContext, TCause>) => string;
|
|
85
|
-
/**
|
|
86
|
-
* Helper type that determines optionality based on whether T includes undefined.
|
|
87
|
-
*/
|
|
88
|
-
type OptionalIfUndefined<T, TKey extends string> = undefined extends T ? { [K in TKey]?: Exclude<T, undefined> } : { [K in TKey]: T };
|
|
89
|
-
/**
|
|
90
|
-
* Input type for error factory functions.
|
|
91
|
-
* `message` is optional (computed from template when omitted).
|
|
92
|
-
* `context` and `cause` follow the same optionality rules as before.
|
|
93
|
-
*/
|
|
94
|
-
type ErrorCallInput<TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = {
|
|
95
|
-
message?: string;
|
|
96
|
-
} & (TContext extends undefined ? Record<never, never> : OptionalIfUndefined<TContext, "context">) & (TCause extends undefined ? Record<never, never> : OptionalIfUndefined<TCause, "cause">);
|
|
97
|
-
/**
|
|
98
|
-
* The final factories object returned by `.withMessage()`.
|
|
99
|
-
* Has factory functions, NO chain methods.
|
|
100
|
-
*/
|
|
101
|
-
type FinalFactories<TName extends `${string}Error`, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = { [K in TName]: (input: ErrorCallInput<TContext, TCause>) => TaggedError<TName, TContext, TCause> } & { [K in ReplaceErrorWithErr<TName>]: (input: ErrorCallInput<TContext, TCause>) => Err<TaggedError<TName, TContext, TCause>> };
|
|
102
|
-
/**
|
|
103
|
-
* Builder interface for the fluent createTaggedError API.
|
|
104
|
-
* Has chain methods only, NO factory functions.
|
|
105
|
-
* Must call `.withMessage(fn)` to get factories.
|
|
106
|
-
*/
|
|
107
|
-
type ErrorBuilder<TName extends `${string}Error`, TContext extends JsonObject | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = {
|
|
108
|
-
/**
|
|
109
|
-
* Constrains the context type for this error.
|
|
110
|
-
* `.withContext<T>()` where T doesn't include undefined → context is **required**
|
|
111
|
-
* `.withContext<T | undefined>()` → context is **optional** but typed when provided
|
|
112
|
-
*/
|
|
113
|
-
withContext<T extends JsonObject | undefined = JsonObject | undefined>(): ErrorBuilder<TName, T, TCause>;
|
|
114
|
-
/**
|
|
115
|
-
* Constrains the cause type for this error.
|
|
116
|
-
* `.withCause<T>()` where T doesn't include undefined → cause is **required**
|
|
117
|
-
* `.withCause<T | undefined>()` → cause is **optional** but typed when provided
|
|
118
|
-
*/
|
|
119
|
-
withCause<T extends AnyTaggedError | undefined = AnyTaggedError | undefined>(): ErrorBuilder<TName, TContext, T>;
|
|
120
|
-
/**
|
|
121
|
-
* Terminal method that defines how the error message is computed from its data.
|
|
122
|
-
* Returns the factory functions — this is the only way to get them.
|
|
123
|
-
*
|
|
124
|
-
* @param fn - Template function that receives `{ name, context?, cause? }` and returns a message string
|
|
125
|
-
*/
|
|
126
|
-
withMessage(fn: MessageFn<TName, TContext, TCause>): FinalFactories<TName, TContext, TCause>;
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* Creates a new tagged error type with a fluent builder API.
|
|
130
|
-
*
|
|
131
|
-
* The builder provides `.withContext<T>()`, `.withCause<T>()`, and `.withMessage(fn)`.
|
|
132
|
-
* `.withMessage(fn)` is **required** and **terminal** — it returns the factory functions.
|
|
133
|
-
*
|
|
134
|
-
* @example Simple error
|
|
135
|
-
* ```ts
|
|
136
|
-
* const { RecorderBusyError, RecorderBusyErr } = createTaggedError('RecorderBusyError')
|
|
137
|
-
* .withMessage(() => 'A recording is already in progress');
|
|
138
|
-
* ```
|
|
139
|
-
*
|
|
140
|
-
* @example Error with context
|
|
141
|
-
* ```ts
|
|
142
|
-
* const { DbNotFoundError, DbNotFoundErr } = createTaggedError('DbNotFoundError')
|
|
143
|
-
* .withContext<{ table: string; id: string }>()
|
|
144
|
-
* .withMessage(({ context }) => `${context.table} '${context.id}' not found`);
|
|
145
|
-
* ```
|
|
146
|
-
*
|
|
147
|
-
* @example Error with context and cause
|
|
148
|
-
* ```ts
|
|
149
|
-
* const { ServiceError, ServiceErr } = createTaggedError('ServiceError')
|
|
150
|
-
* .withContext<{ operation: string }>()
|
|
151
|
-
* .withCause<DbServiceError>()
|
|
152
|
-
* .withMessage(({ context, cause }) =>
|
|
153
|
-
* `Operation '${context.operation}' failed: ${cause.message}`
|
|
154
|
-
* );
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
declare function createTaggedError<TName extends `${string}Error`>(name: TName): ErrorBuilder<TName>;
|
|
92
|
+
//# sourceMappingURL=extractErrorMessage.d.ts.map
|
|
93
|
+
|
|
158
94
|
//#endregion
|
|
159
|
-
export { AnyTaggedError, JsonObject, JsonValue,
|
|
95
|
+
export { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, JsonObject, JsonValue, ValidatedConfig, defineErrors, extractErrorMessage };
|
|
160
96
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/defineErrors.ts","../../src/error/extractErrorMessage.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/error/index.js
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
import { Err } from "../result-DnOm5ds5.js";
|
|
2
2
|
|
|
3
|
-
//#region src/error/
|
|
3
|
+
//#region src/error/defineErrors.ts
|
|
4
|
+
/**
|
|
5
|
+
* Defines a set of typed error factories using Rust-style namespaced variants.
|
|
6
|
+
*
|
|
7
|
+
* Each key is a short variant name (the namespace provides context). Every
|
|
8
|
+
* factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
|
|
9
|
+
* handlers. The variant name is stamped as `name` on the error object.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const HttpError = defineErrors({
|
|
14
|
+
* Connection: ({ cause }: { cause: string }) => ({
|
|
15
|
+
* message: `Failed to connect: ${cause}`,
|
|
16
|
+
* cause,
|
|
17
|
+
* }),
|
|
18
|
+
* Parse: ({ cause }: { cause: string }) => ({
|
|
19
|
+
* message: `Failed to parse: ${cause}`,
|
|
20
|
+
* cause,
|
|
21
|
+
* }),
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* type HttpError = InferErrors<typeof HttpError>;
|
|
25
|
+
*
|
|
26
|
+
* const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function defineErrors(config) {
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
|
|
32
|
+
const body = ctor(...args);
|
|
33
|
+
return Err(Object.freeze({
|
|
34
|
+
...body,
|
|
35
|
+
name
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/error/extractErrorMessage.ts
|
|
4
43
|
/**
|
|
5
44
|
* Extracts a readable error message from an unknown error value
|
|
6
45
|
*
|
|
@@ -34,70 +73,7 @@ function extractErrorMessage(error) {
|
|
|
34
73
|
}
|
|
35
74
|
return String(error);
|
|
36
75
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Creates a new tagged error type with a fluent builder API.
|
|
39
|
-
*
|
|
40
|
-
* The builder provides `.withContext<T>()`, `.withCause<T>()`, and `.withMessage(fn)`.
|
|
41
|
-
* `.withMessage(fn)` is **required** and **terminal** — it returns the factory functions.
|
|
42
|
-
*
|
|
43
|
-
* @example Simple error
|
|
44
|
-
* ```ts
|
|
45
|
-
* const { RecorderBusyError, RecorderBusyErr } = createTaggedError('RecorderBusyError')
|
|
46
|
-
* .withMessage(() => 'A recording is already in progress');
|
|
47
|
-
* ```
|
|
48
|
-
*
|
|
49
|
-
* @example Error with context
|
|
50
|
-
* ```ts
|
|
51
|
-
* const { DbNotFoundError, DbNotFoundErr } = createTaggedError('DbNotFoundError')
|
|
52
|
-
* .withContext<{ table: string; id: string }>()
|
|
53
|
-
* .withMessage(({ context }) => `${context.table} '${context.id}' not found`);
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* @example Error with context and cause
|
|
57
|
-
* ```ts
|
|
58
|
-
* const { ServiceError, ServiceErr } = createTaggedError('ServiceError')
|
|
59
|
-
* .withContext<{ operation: string }>()
|
|
60
|
-
* .withCause<DbServiceError>()
|
|
61
|
-
* .withMessage(({ context, cause }) =>
|
|
62
|
-
* `Operation '${context.operation}' failed: ${cause.message}`
|
|
63
|
-
* );
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
function createTaggedError(name) {
|
|
67
|
-
const createBuilder = () => {
|
|
68
|
-
return {
|
|
69
|
-
withContext() {
|
|
70
|
-
return createBuilder();
|
|
71
|
-
},
|
|
72
|
-
withCause() {
|
|
73
|
-
return createBuilder();
|
|
74
|
-
},
|
|
75
|
-
withMessage(fn) {
|
|
76
|
-
const errorConstructor = (input) => {
|
|
77
|
-
const messageInput = {
|
|
78
|
-
name,
|
|
79
|
-
..."context" in input ? { context: input.context } : {},
|
|
80
|
-
..."cause" in input ? { cause: input.cause } : {}
|
|
81
|
-
};
|
|
82
|
-
return {
|
|
83
|
-
name,
|
|
84
|
-
message: input.message ?? fn(messageInput),
|
|
85
|
-
..."context" in input ? { context: input.context } : {},
|
|
86
|
-
..."cause" in input ? { cause: input.cause } : {}
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
const errName = name.replace(/Error$/, "Err");
|
|
90
|
-
const errConstructor = (input) => Err(errorConstructor(input));
|
|
91
|
-
return {
|
|
92
|
-
[name]: errorConstructor,
|
|
93
|
-
[errName]: errConstructor
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
return createBuilder();
|
|
99
|
-
}
|
|
100
76
|
|
|
101
77
|
//#endregion
|
|
102
|
-
export {
|
|
78
|
+
export { defineErrors, extractErrorMessage };
|
|
103
79
|
//# sourceMappingURL=index.js.map
|
package/dist/error/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["error: unknown","name: TName","fn: MessageFn<TName, TContext, TCause>","input: ErrorCallInput<TContext, TCause>"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type {\n\tTaggedError,\n\tAnyTaggedError,\n\tJsonObject,\n} from \"./types.js\";\nimport { Err } from \"../result/result.js\";\n\n/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n\n/**\n * Replaces the \"Error\" suffix with \"Err\" suffix in error type names.\n */\ntype ReplaceErrorWithErr<T extends `${string}Error`> =\n\tT extends `${infer TBase}Error` ? `${TBase}Err` : never;\n\n// =============================================================================\n// Message Function Types\n// =============================================================================\n\n/**\n * Input provided to the message template function.\n * Contains everything the error will have except `message` (since that's what it computes).\n */\ntype MessageInput<\n\tTName extends string,\n\tTContext extends JsonObject | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = { name: TName } & ([TContext] extends [undefined]\n\t? Record<never, never>\n\t: { context: Exclude<TContext, undefined> }) &\n\t([TCause] extends [undefined]\n\t\t? Record<never, never>\n\t\t: { cause: Exclude<TCause, undefined> });\n\n/**\n * Message template function type.\n */\ntype MessageFn<\n\tTName extends string,\n\tTContext extends JsonObject | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = (input: MessageInput<TName, TContext, TCause>) => string;\n\n// =============================================================================\n// Factory Input Types\n// =============================================================================\n\n/**\n * Helper type that determines optionality based on whether T includes undefined.\n */\ntype OptionalIfUndefined<T, TKey extends string> = undefined extends T\n\t? { [K in TKey]?: Exclude<T, undefined> }\n\t: { [K in TKey]: T };\n\n/**\n * Input type for error factory functions.\n * `message` is optional (computed from template when omitted).\n * `context` and `cause` follow the same optionality rules as before.\n */\ntype ErrorCallInput<\n\tTContext extends JsonObject | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = { message?: string } & (TContext extends undefined\n\t? Record<never, never>\n\t: OptionalIfUndefined<TContext, \"context\">) &\n\t(TCause extends undefined\n\t\t? Record<never, never>\n\t\t: OptionalIfUndefined<TCause, \"cause\">);\n\n// =============================================================================\n// Builder & Factory Types\n// =============================================================================\n\n/**\n * The final factories object returned by `.withMessage()`.\n * Has factory functions, NO chain methods.\n */\ntype FinalFactories<\n\tTName extends `${string}Error`,\n\tTContext extends JsonObject | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = {\n\t[K in TName]: (\n\t\tinput: ErrorCallInput<TContext, TCause>,\n\t) => TaggedError<TName, TContext, TCause>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: (\n\t\tinput: ErrorCallInput<TContext, TCause>,\n\t) => Err<TaggedError<TName, TContext, TCause>>;\n};\n\n/**\n * Builder interface for the fluent createTaggedError API.\n * Has chain methods only, NO factory functions.\n * Must call `.withMessage(fn)` to get factories.\n */\ntype ErrorBuilder<\n\tTName extends `${string}Error`,\n\tTContext extends JsonObject | undefined = undefined,\n\tTCause extends AnyTaggedError | undefined = undefined,\n> = {\n\t/**\n\t * Constrains the context type for this error.\n\t * `.withContext<T>()` where T doesn't include undefined → context is **required**\n\t * `.withContext<T | undefined>()` → context is **optional** but typed when provided\n\t */\n\twithContext<\n\t\tT extends JsonObject | undefined = JsonObject | undefined,\n\t>(): ErrorBuilder<TName, T, TCause>;\n\n\t/**\n\t * Constrains the cause type for this error.\n\t * `.withCause<T>()` where T doesn't include undefined → cause is **required**\n\t * `.withCause<T | undefined>()` → cause is **optional** but typed when provided\n\t */\n\twithCause<\n\t\tT extends AnyTaggedError | undefined = AnyTaggedError | undefined,\n\t>(): ErrorBuilder<TName, TContext, T>;\n\n\t/**\n\t * Terminal method that defines how the error message is computed from its data.\n\t * Returns the factory functions — this is the only way to get them.\n\t *\n\t * @param fn - Template function that receives `{ name, context?, cause? }` and returns a message string\n\t */\n\twithMessage(\n\t\tfn: MessageFn<TName, TContext, TCause>,\n\t): FinalFactories<TName, TContext, TCause>;\n};\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Creates a new tagged error type with a fluent builder API.\n *\n * The builder provides `.withContext<T>()`, `.withCause<T>()`, and `.withMessage(fn)`.\n * `.withMessage(fn)` is **required** and **terminal** — it returns the factory functions.\n *\n * @example Simple error\n * ```ts\n * const { RecorderBusyError, RecorderBusyErr } = createTaggedError('RecorderBusyError')\n * .withMessage(() => 'A recording is already in progress');\n * ```\n *\n * @example Error with context\n * ```ts\n * const { DbNotFoundError, DbNotFoundErr } = createTaggedError('DbNotFoundError')\n * .withContext<{ table: string; id: string }>()\n * .withMessage(({ context }) => `${context.table} '${context.id}' not found`);\n * ```\n *\n * @example Error with context and cause\n * ```ts\n * const { ServiceError, ServiceErr } = createTaggedError('ServiceError')\n * .withContext<{ operation: string }>()\n * .withCause<DbServiceError>()\n * .withMessage(({ context, cause }) =>\n * `Operation '${context.operation}' failed: ${cause.message}`\n * );\n * ```\n */\nexport function createTaggedError<TName extends `${string}Error`>(\n\tname: TName,\n): ErrorBuilder<TName> {\n\tconst createBuilder = <\n\t\tTContext extends JsonObject | undefined = undefined,\n\t\tTCause extends AnyTaggedError | undefined = undefined,\n\t>(): ErrorBuilder<TName, TContext, TCause> => {\n\t\treturn {\n\t\t\twithContext<\n\t\t\t\tT extends JsonObject | undefined = JsonObject | undefined,\n\t\t\t>() {\n\t\t\t\treturn createBuilder<T, TCause>();\n\t\t\t},\n\t\t\twithCause<\n\t\t\t\tT extends AnyTaggedError | undefined = AnyTaggedError | undefined,\n\t\t\t>() {\n\t\t\t\treturn createBuilder<TContext, T>();\n\t\t\t},\n\t\t\twithMessage(\n\t\t\t\tfn: MessageFn<TName, TContext, TCause>,\n\t\t\t): FinalFactories<TName, TContext, TCause> {\n\t\t\t\tconst errorConstructor = (\n\t\t\t\t\tinput: ErrorCallInput<TContext, TCause>,\n\t\t\t\t) => {\n\t\t\t\t\tconst messageInput = {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\t...(\"context\" in input ? { context: input.context } : {}),\n\t\t\t\t\t\t...(\"cause\" in input ? { cause: input.cause } : {}),\n\t\t\t\t\t} as MessageInput<TName, TContext, TCause>;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\tinput.message ?? fn(messageInput),\n\t\t\t\t\t\t...(\"context\" in input ? { context: input.context } : {}),\n\t\t\t\t\t\t...(\"cause\" in input ? { cause: input.cause } : {}),\n\t\t\t\t\t} as unknown as TaggedError<TName, TContext, TCause>;\n\t\t\t\t};\n\n\t\t\t\tconst errName = name.replace(\n\t\t\t\t\t/Error$/,\n\t\t\t\t\t\"Err\",\n\t\t\t\t) as ReplaceErrorWithErr<TName>;\n\t\t\t\tconst errConstructor = (\n\t\t\t\t\tinput: ErrorCallInput<TContext, TCause>,\n\t\t\t\t) => Err(errorConstructor(input));\n\n\t\t\t\treturn {\n\t\t\t\t\t[name]: errorConstructor,\n\t\t\t\t\t[errName]: errConstructor,\n\t\t\t\t} as FinalFactories<TName, TContext, TCause>;\n\t\t\t},\n\t\t} as ErrorBuilder<TName, TContext, TCause>;\n\t};\n\n\treturn createBuilder();\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,oBAAoBA,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4JD,SAAgB,kBACfC,MACsB;CACtB,MAAM,gBAAgB,MAGwB;AAC7C,SAAO;GACN,cAEI;AACH,WAAO,eAA0B;GACjC;GACD,YAEI;AACH,WAAO,eAA4B;GACnC;GACD,YACCC,IAC0C;IAC1C,MAAM,mBAAmB,CACxBC,UACI;KACJ,MAAM,eAAe;MACpB;MACA,GAAI,aAAa,QAAQ,EAAE,SAAS,MAAM,QAAS,IAAG,CAAE;MACxD,GAAI,WAAW,QAAQ,EAAE,OAAO,MAAM,MAAO,IAAG,CAAE;KAClD;AAED,YAAO;MACN;MACA,SACC,MAAM,WAAW,GAAG,aAAa;MAClC,GAAI,aAAa,QAAQ,EAAE,SAAS,MAAM,QAAS,IAAG,CAAE;MACxD,GAAI,WAAW,QAAQ,EAAE,OAAO,MAAM,MAAO,IAAG,CAAE;KAClD;IACD;IAED,MAAM,UAAU,KAAK,QACpB,UACA,MACA;IACD,MAAM,iBAAiB,CACtBA,UACI,IAAI,iBAAiB,MAAM,CAAC;AAEjC,WAAO;MACL,OAAO;MACP,UAAU;IACX;GACD;EACD;CACD;AAED,QAAO,eAAe;AACtB"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["config: TConfig & ValidatedConfig<TConfig>","result: Record<string, unknown>","error: unknown"],"sources":["../../src/error/defineErrors.ts","../../src/error/extractErrorMessage.ts"],"sourcesContent":["import { Err } from \"../result/result.js\";\nimport type {\n\tDefineErrorsReturn,\n\tErrorsConfig,\n\tValidatedConfig,\n} from \"./types.js\";\n\n/**\n * Defines a set of typed error factories using Rust-style namespaced variants.\n *\n * Each key is a short variant name (the namespace provides context). Every\n * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch\n * handlers. The variant name is stamped as `name` on the error object.\n *\n * @example\n * ```ts\n * const HttpError = defineErrors({\n * Connection: ({ cause }: { cause: string }) => ({\n * message: `Failed to connect: ${cause}`,\n * cause,\n * }),\n * Parse: ({ cause }: { cause: string }) => ({\n * message: `Failed to parse: ${cause}`,\n * cause,\n * }),\n * });\n *\n * type HttpError = InferErrors<typeof HttpError>;\n *\n * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>\n * ```\n */\nexport function defineErrors<const TConfig extends ErrorsConfig>(\n\tconfig: TConfig & ValidatedConfig<TConfig>,\n): DefineErrorsReturn<TConfig> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [name, ctor] of Object.entries(config)) {\n\t\tresult[name] = (...args: unknown[]) => {\n\t\t\tconst body = (ctor as (...a: unknown[]) => Record<string, unknown>)(\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn Err(Object.freeze({ ...body, name }));\n\t\t};\n\t}\n\n\treturn result as DefineErrorsReturn<TConfig>;\n}\n","/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,aACfA,QAC8B;CAC9B,MAAMC,SAAkC,CAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,KAAK,IAAI,OAAO,QAAQ,OAAO,CAChD,QAAO,QAAQ,CAAC,GAAG,SAAoB;EACtC,MAAM,OAAO,AAAC,KACb,GAAG,KACH;AACD,SAAO,IAAI,OAAO,OAAO;GAAE,GAAG;GAAM;EAAM,EAAC,CAAC;CAC5C;AAGF,QAAO;AACP;;;;;;;;;;ACzCD,SAAgB,oBAAoBC,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB"}
|