wellcrafted 0.23.1 → 0.25.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 +18 -7
- package/dist/error/index.d.ts +181 -121
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +3 -48
- package/dist/error/index.js.map +1 -1
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -345,13 +345,24 @@ The `catch` parameter in `trySync` and `tryAsync` enables smart return type narr
|
|
|
345
345
|
|
|
346
346
|
### Recovery Pattern (Always Succeeds)
|
|
347
347
|
```typescript
|
|
348
|
-
//
|
|
349
|
-
|
|
348
|
+
// ❌ Before: Mutable variable required
|
|
349
|
+
let parsed: unknown;
|
|
350
|
+
try {
|
|
351
|
+
parsed = JSON.parse(riskyJson);
|
|
352
|
+
} catch {
|
|
353
|
+
parsed = [];
|
|
354
|
+
}
|
|
355
|
+
// Now use parsed...
|
|
356
|
+
|
|
357
|
+
// ✅ After: Clean, immutable pattern
|
|
358
|
+
const { data: parsed } = trySync({
|
|
350
359
|
try: () => JSON.parse(riskyJson),
|
|
351
|
-
catch: () => Ok(
|
|
360
|
+
catch: () => Ok([])
|
|
352
361
|
});
|
|
353
|
-
//
|
|
354
|
-
|
|
362
|
+
// parsed is always defined and type-safe!
|
|
363
|
+
|
|
364
|
+
// When catch always returns Ok<T>, the function returns Ok<T>
|
|
365
|
+
// This means no error checking needed - you can safely destructure and use data directly
|
|
355
366
|
```
|
|
356
367
|
|
|
357
368
|
### Propagation Pattern (May Fail)
|
|
@@ -359,7 +370,7 @@ console.log(alwaysSucceeds.data); // Safe to access directly
|
|
|
359
370
|
// When catch can return Err<E>, function returns Result<T, E>
|
|
360
371
|
const mayFail = trySync({
|
|
361
372
|
try: () => JSON.parse(riskyJson),
|
|
362
|
-
catch: (error) =>
|
|
373
|
+
catch: (error) => ParseErr({ message: "Invalid JSON", cause: error })
|
|
363
374
|
});
|
|
364
375
|
// mayFail: Result<object, ParseError> - Must check for errors
|
|
365
376
|
if (isOk(mayFail)) {
|
|
@@ -377,7 +388,7 @@ const smartParse = trySync({
|
|
|
377
388
|
return Ok({}); // Return Ok<T> for fallback
|
|
378
389
|
}
|
|
379
390
|
// Propagate other errors
|
|
380
|
-
return
|
|
391
|
+
return ParseErr({ message: "Parse failed", cause: error });
|
|
381
392
|
}
|
|
382
393
|
});
|
|
383
394
|
// smartParse: Result<object, ParseError> - Mixed handling = Result type
|
package/dist/error/index.d.ts
CHANGED
|
@@ -2,6 +2,22 @@ import { Err } from "../result-DRq8PMRe.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/error/types.d.ts
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Helper type that adds a context property only when TContext is not never.
|
|
7
|
+
* When TContext is never, returns an empty object (no context property).
|
|
8
|
+
* When TContext is a real type, returns { context: TContext } (required property).
|
|
9
|
+
*/
|
|
10
|
+
type WithContext<TContext> = [TContext] extends [never] ? {} : {
|
|
11
|
+
context: TContext;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Helper type that adds a cause property only when TCause is not never.
|
|
15
|
+
* When TCause is never, returns an empty object (no cause property).
|
|
16
|
+
* When TCause is a real type, returns { cause: TCause } (required property).
|
|
17
|
+
*/
|
|
18
|
+
type WithCause<TCause> = [TCause] extends [never] ? {} : {
|
|
19
|
+
cause: TCause;
|
|
20
|
+
};
|
|
5
21
|
/**
|
|
6
22
|
* Creates a tagged error type for type-safe error handling.
|
|
7
23
|
* Uses the `name` property as a discriminator for tagged unions.
|
|
@@ -10,75 +26,44 @@ import { Err } from "../result-DRq8PMRe.js";
|
|
|
10
26
|
* call stack. Each error wraps its cause, building a complete trace of how
|
|
11
27
|
* an error propagated through your application layers.
|
|
12
28
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
29
|
+
* **Type Parameter Behavior:**
|
|
30
|
+
* - When `TContext` is `never` (default): No `context` property exists
|
|
31
|
+
* - When `TContext` is specified: `context` is a **required** property
|
|
32
|
+
* - When `TCause` is `never` (default): No `cause` property exists
|
|
33
|
+
* - When `TCause` is specified: `cause` is a **required** property
|
|
34
|
+
*
|
|
35
|
+
* @template TName - The error name (discriminator for tagged unions)
|
|
36
|
+
* @template TContext - Additional context data for the error (default: never = no context property)
|
|
37
|
+
* @template TCause - The type of error that caused this error (default: never = no cause property)
|
|
15
38
|
*
|
|
16
39
|
* @example
|
|
17
40
|
* ```ts
|
|
18
|
-
* // Simple error without cause
|
|
41
|
+
* // Simple error without context or cause (properties don't exist)
|
|
19
42
|
* type ValidationError = TaggedError<"ValidationError">;
|
|
20
43
|
* const validationError: ValidationError = {
|
|
21
44
|
* name: "ValidationError",
|
|
22
45
|
* message: "Input is required"
|
|
23
46
|
* };
|
|
47
|
+
* // validationError.context // Property 'context' does not exist
|
|
24
48
|
*
|
|
25
|
-
* //
|
|
26
|
-
* type NetworkError = TaggedError<"NetworkError">;
|
|
27
|
-
* type DatabaseError = TaggedError<"DatabaseError", NetworkError>;
|
|
28
|
-
* type ServiceError = TaggedError<"ServiceError", DatabaseError>;
|
|
29
|
-
* type APIError = TaggedError<"APIError", ServiceError>;
|
|
30
|
-
*
|
|
49
|
+
* // Error with required context
|
|
50
|
+
* type NetworkError = TaggedError<"NetworkError", { host: string; port: number }>;
|
|
31
51
|
* const networkError: NetworkError = {
|
|
32
52
|
* name: "NetworkError",
|
|
33
|
-
* message: "Socket timeout
|
|
34
|
-
* context: { host: "db.example.com", port: 5432
|
|
53
|
+
* message: "Socket timeout",
|
|
54
|
+
* context: { host: "db.example.com", port: 5432 } // Required!
|
|
35
55
|
* };
|
|
56
|
+
* const host = networkError.context.host; // No optional chaining needed
|
|
36
57
|
*
|
|
58
|
+
* // Type-safe error chaining with required cause
|
|
59
|
+
* type DatabaseError = TaggedError<"DatabaseError", { operation: string }, NetworkError>;
|
|
37
60
|
* const dbError: DatabaseError = {
|
|
38
61
|
* name: "DatabaseError",
|
|
39
62
|
* message: "Failed to connect to database",
|
|
40
|
-
* context: { operation: "connect",
|
|
41
|
-
* cause: networkError //
|
|
63
|
+
* context: { operation: "connect" }, // Required!
|
|
64
|
+
* cause: networkError // Required!
|
|
42
65
|
* };
|
|
43
66
|
*
|
|
44
|
-
* const serviceError: ServiceError = {
|
|
45
|
-
* name: "UserServiceError",
|
|
46
|
-
* message: "Could not fetch user profile",
|
|
47
|
-
* context: { userId: "123", method: "getUserById" },
|
|
48
|
-
* cause: dbError // TypeScript enforces this must be DatabaseError
|
|
49
|
-
* };
|
|
50
|
-
*
|
|
51
|
-
* const apiError: APIError = {
|
|
52
|
-
* name: "APIError",
|
|
53
|
-
* message: "Internal server error",
|
|
54
|
-
* context: { endpoint: "/api/users/123", statusCode: 500 },
|
|
55
|
-
* cause: serviceError // TypeScript enforces this must be ServiceError
|
|
56
|
-
* };
|
|
57
|
-
*
|
|
58
|
-
* // The entire error chain is JSON-serializable and type-safe
|
|
59
|
-
* console.log(JSON.stringify(apiError, null, 2));
|
|
60
|
-
* // Outputs:
|
|
61
|
-
* // {
|
|
62
|
-
* // "name": "APIError",
|
|
63
|
-
* // "message": "Internal server error",
|
|
64
|
-
* // "context": { "endpoint": "/api/users/123", "statusCode": 500 },
|
|
65
|
-
* // "cause": {
|
|
66
|
-
* // "name": "UserServiceError",
|
|
67
|
-
* // "message": "Could not fetch user profile",
|
|
68
|
-
* // "context": { "userId": "123", "method": "getUserById" },
|
|
69
|
-
* // "cause": {
|
|
70
|
-
* // "name": "DatabaseError",
|
|
71
|
-
* // "message": "Failed to connect to database",
|
|
72
|
-
* // "context": { "operation": "connect", "retries": 3 },
|
|
73
|
-
* // "cause": {
|
|
74
|
-
* // "name": "NetworkError",
|
|
75
|
-
* // "message": "Socket timeout after 5000ms",
|
|
76
|
-
* // "context": { "host": "db.example.com", "port": 5432, "timeout": 5000 }
|
|
77
|
-
* // }
|
|
78
|
-
* // }
|
|
79
|
-
* // }
|
|
80
|
-
* // }
|
|
81
|
-
*
|
|
82
67
|
* // Discriminated unions still work
|
|
83
68
|
* function handleError(error: ValidationError | NetworkError) {
|
|
84
69
|
* switch (error.name) {
|
|
@@ -88,38 +73,12 @@ import { Err } from "../result-DRq8PMRe.js";
|
|
|
88
73
|
* break;
|
|
89
74
|
* }
|
|
90
75
|
* }
|
|
91
|
-
*
|
|
92
|
-
* // Used in Result types:
|
|
93
|
-
* function validate(input: string): Result<string, ValidationError> {
|
|
94
|
-
* if (!input) {
|
|
95
|
-
* return Err({
|
|
96
|
-
* name: "ValidationError",
|
|
97
|
-
* message: "Input is required",
|
|
98
|
-
* context: { input }
|
|
99
|
-
* });
|
|
100
|
-
* }
|
|
101
|
-
* return Ok(input);
|
|
102
|
-
* }
|
|
103
|
-
*
|
|
104
|
-
* // Context is optional - omit when not needed:
|
|
105
|
-
* function checkAuth(): Result<User, AuthError> {
|
|
106
|
-
* if (!isAuthenticated) {
|
|
107
|
-
* return Err({
|
|
108
|
-
* name: "AuthError",
|
|
109
|
-
* message: "User not authenticated"
|
|
110
|
-
* });
|
|
111
|
-
* }
|
|
112
|
-
* return Ok(currentUser);
|
|
113
|
-
* }
|
|
114
76
|
* ```
|
|
115
77
|
*/
|
|
116
|
-
type TaggedError<TName extends string = string,
|
|
78
|
+
type TaggedError<TName extends string = string, TContext = never, TCause = never> = Readonly<{
|
|
117
79
|
name: TName;
|
|
118
80
|
message: string;
|
|
119
|
-
|
|
120
|
-
cause?: TCause;
|
|
121
|
-
}>;
|
|
122
|
-
//# sourceMappingURL=types.d.ts.map
|
|
81
|
+
} & WithContext<TContext> & WithCause<TCause>>;
|
|
123
82
|
//#endregion
|
|
124
83
|
//#region src/error/utils.d.ts
|
|
125
84
|
/**
|
|
@@ -159,15 +118,15 @@ type TaggedError<TName extends string = string, TCause extends TaggedError<strin
|
|
|
159
118
|
*/
|
|
160
119
|
declare function extractErrorMessage(error: unknown): string;
|
|
161
120
|
/**
|
|
162
|
-
*
|
|
121
|
+
* Base type for any tagged error, used as a constraint for cause parameters.
|
|
163
122
|
*/
|
|
164
|
-
type
|
|
123
|
+
type AnyTaggedError = {
|
|
124
|
+
name: string;
|
|
125
|
+
message: string;
|
|
126
|
+
};
|
|
165
127
|
/**
|
|
166
128
|
* Replaces the "Error" suffix with "Err" suffix in error type names.
|
|
167
129
|
*
|
|
168
|
-
* This utility type is used to create companion function names for error constructors
|
|
169
|
-
* that return Err-wrapped results, maintaining consistent naming conventions.
|
|
170
|
-
*
|
|
171
130
|
* @template T - An error type name that must end with "Error"
|
|
172
131
|
* @returns The type name with "Error" replaced by "Err"
|
|
173
132
|
*
|
|
@@ -175,64 +134,165 @@ type TaggedErrorWithoutName<TName extends string, TCause extends TaggedError<str
|
|
|
175
134
|
* ```ts
|
|
176
135
|
* type NetworkErr = ReplaceErrorWithErr<"NetworkError">; // "NetworkErr"
|
|
177
136
|
* type ValidationErr = ReplaceErrorWithErr<"ValidationError">; // "ValidationErr"
|
|
178
|
-
* type AuthErr = ReplaceErrorWithErr<"AuthError">; // "AuthErr"
|
|
179
137
|
* ```
|
|
180
138
|
*/
|
|
181
139
|
type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
|
|
182
140
|
/**
|
|
183
|
-
* Return type
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
141
|
+
* Return type when neither context nor cause are constrained.
|
|
142
|
+
* Both factory functions accept any context and cause at call time.
|
|
143
|
+
*/
|
|
144
|
+
type FlexibleFactories<TName extends `${string}Error`> = { [K in TName]: FlexibleErrorConstructor<K> } & { [K in ReplaceErrorWithErr<TName>]: FlexibleErrConstructor<TName> };
|
|
145
|
+
/**
|
|
146
|
+
* Return type when context is fixed but cause is flexible.
|
|
147
|
+
* Context shape is locked, but cause can be any TaggedError at call time.
|
|
148
|
+
*/
|
|
149
|
+
type ContextFixedFactories<TName extends `${string}Error`, TContext extends Record<string, unknown>> = { [K in TName]: ContextFixedErrorConstructor<K, TContext> } & { [K in ReplaceErrorWithErr<TName>]: ContextFixedErrConstructor<TName, TContext> };
|
|
150
|
+
/**
|
|
151
|
+
* Return type when both context and cause are fixed.
|
|
152
|
+
* Both shapes are locked at factory creation time.
|
|
153
|
+
*/
|
|
154
|
+
type BothFixedFactories<TName extends `${string}Error`, TContext extends Record<string, unknown>, TCause extends AnyTaggedError> = { [K in TName]: BothFixedErrorConstructor<K, TContext, TCause> } & { [K in ReplaceErrorWithErr<TName>]: BothFixedErrConstructor<TName, TContext, TCause> };
|
|
155
|
+
/**
|
|
156
|
+
* Creates plain TaggedError objects with flexible context and cause.
|
|
157
|
+
* Uses function overloads to precisely match return types to inputs.
|
|
158
|
+
*/
|
|
159
|
+
type FlexibleErrorConstructor<TName extends string> = {
|
|
160
|
+
(input: {
|
|
161
|
+
message: string;
|
|
162
|
+
}): TaggedError<TName, never, never>;
|
|
163
|
+
<TContext extends Record<string, unknown>>(input: {
|
|
164
|
+
message: string;
|
|
165
|
+
context: TContext;
|
|
166
|
+
}): TaggedError<TName, TContext, never>;
|
|
167
|
+
<TCause extends AnyTaggedError>(input: {
|
|
168
|
+
message: string;
|
|
169
|
+
cause: TCause;
|
|
170
|
+
}): TaggedError<TName, never, TCause>;
|
|
171
|
+
<TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {
|
|
172
|
+
message: string;
|
|
173
|
+
context: TContext;
|
|
174
|
+
cause: TCause;
|
|
175
|
+
}): TaggedError<TName, TContext, TCause>;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Creates Err-wrapped TaggedError objects with flexible context and cause.
|
|
179
|
+
*/
|
|
180
|
+
type FlexibleErrConstructor<TName extends string> = {
|
|
181
|
+
(input: {
|
|
182
|
+
message: string;
|
|
183
|
+
}): Err<TaggedError<TName, never, never>>;
|
|
184
|
+
<TContext extends Record<string, unknown>>(input: {
|
|
185
|
+
message: string;
|
|
186
|
+
context: TContext;
|
|
187
|
+
}): Err<TaggedError<TName, TContext, never>>;
|
|
188
|
+
<TCause extends AnyTaggedError>(input: {
|
|
189
|
+
message: string;
|
|
190
|
+
cause: TCause;
|
|
191
|
+
}): Err<TaggedError<TName, never, TCause>>;
|
|
192
|
+
<TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {
|
|
193
|
+
message: string;
|
|
194
|
+
context: TContext;
|
|
195
|
+
cause: TCause;
|
|
196
|
+
}): Err<TaggedError<TName, TContext, TCause>>;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Creates plain TaggedError objects with fixed context but flexible cause.
|
|
200
|
+
* Context is always required. Cause is optional and inferred at call site.
|
|
201
|
+
*/
|
|
202
|
+
type ContextFixedErrorConstructor<TName extends string, TContext extends Record<string, unknown>> = {
|
|
203
|
+
(input: {
|
|
204
|
+
message: string;
|
|
205
|
+
context: TContext;
|
|
206
|
+
}): TaggedError<TName, TContext, never>;
|
|
207
|
+
<TCause extends AnyTaggedError>(input: {
|
|
208
|
+
message: string;
|
|
209
|
+
context: TContext;
|
|
210
|
+
cause: TCause;
|
|
211
|
+
}): TaggedError<TName, TContext, TCause>;
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Creates Err-wrapped TaggedError objects with fixed context but flexible cause.
|
|
188
215
|
*/
|
|
189
|
-
type
|
|
216
|
+
type ContextFixedErrConstructor<TName extends string, TContext extends Record<string, unknown>> = {
|
|
217
|
+
(input: {
|
|
218
|
+
message: string;
|
|
219
|
+
context: TContext;
|
|
220
|
+
}): Err<TaggedError<TName, TContext, never>>;
|
|
221
|
+
<TCause extends AnyTaggedError>(input: {
|
|
222
|
+
message: string;
|
|
223
|
+
context: TContext;
|
|
224
|
+
cause: TCause;
|
|
225
|
+
}): Err<TaggedError<TName, TContext, TCause>>;
|
|
226
|
+
};
|
|
190
227
|
/**
|
|
191
|
-
*
|
|
228
|
+
* Creates plain TaggedError objects with both context and cause fixed.
|
|
229
|
+
* Context is required. Cause is optional but must match the specified type.
|
|
230
|
+
*/
|
|
231
|
+
type BothFixedErrorConstructor<TName extends string, TContext extends Record<string, unknown>, TCause extends AnyTaggedError> = {
|
|
232
|
+
(input: {
|
|
233
|
+
message: string;
|
|
234
|
+
context: TContext;
|
|
235
|
+
}): TaggedError<TName, TContext, never>;
|
|
236
|
+
(input: {
|
|
237
|
+
message: string;
|
|
238
|
+
context: TContext;
|
|
239
|
+
cause: TCause;
|
|
240
|
+
}): TaggedError<TName, TContext, TCause>;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Creates Err-wrapped TaggedError objects with both context and cause fixed.
|
|
244
|
+
*/
|
|
245
|
+
type BothFixedErrConstructor<TName extends string, TContext extends Record<string, unknown>, TCause extends AnyTaggedError> = {
|
|
246
|
+
(input: {
|
|
247
|
+
message: string;
|
|
248
|
+
context: TContext;
|
|
249
|
+
}): Err<TaggedError<TName, TContext, never>>;
|
|
250
|
+
(input: {
|
|
251
|
+
message: string;
|
|
252
|
+
context: TContext;
|
|
253
|
+
cause: TCause;
|
|
254
|
+
}): Err<TaggedError<TName, TContext, TCause>>;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Creates two factory functions for building tagged errors with type-safe error chaining.
|
|
192
258
|
*
|
|
193
259
|
* Given an error name like "NetworkError", this returns:
|
|
194
260
|
* - `NetworkError`: Creates a plain TaggedError object
|
|
195
261
|
* - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
|
|
196
262
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
263
|
+
* **Three usage modes:**
|
|
264
|
+
*
|
|
265
|
+
* 1. **Flexible mode** (no type params): Context and cause are optional, any shape accepted
|
|
266
|
+
* 2. **Fixed context mode** (TContext specified): Context is required with that exact shape
|
|
267
|
+
* 3. **Both fixed mode** (TContext + TCause): Context required, cause (if provided) must match
|
|
199
268
|
*
|
|
269
|
+
* @template TName - The name of the error type (must end with "Error")
|
|
270
|
+
* @template TContext - Optional fixed context shape (makes context required)
|
|
271
|
+
* @template TCause - Optional fixed cause type (constrains cause type if provided)
|
|
200
272
|
* @param name - The name of the error type (must end with "Error")
|
|
201
|
-
* @returns An object with two factory functions:
|
|
202
|
-
* - [name]: Function that creates plain TaggedError objects
|
|
203
|
-
* - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
|
|
204
273
|
*
|
|
205
274
|
* @example
|
|
206
275
|
* ```ts
|
|
207
|
-
* //
|
|
276
|
+
* // Mode 1: Flexible - context optional, any shape
|
|
208
277
|
* const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
|
|
278
|
+
* NetworkError({ message: 'Connection failed' });
|
|
279
|
+
* NetworkError({ message: 'Timeout', context: { url: 'https://...' } });
|
|
209
280
|
*
|
|
210
|
-
* //
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* });
|
|
215
|
-
* // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
|
|
216
|
-
*
|
|
217
|
-
* // NetworkErr: Creates error and wraps in Err result
|
|
218
|
-
* return NetworkErr({
|
|
219
|
-
* message: 'Connection failed',
|
|
220
|
-
* context: { url: 'https://api.example.com' }
|
|
221
|
-
* });
|
|
222
|
-
* // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
|
|
281
|
+
* // Mode 2: Fixed context - context REQUIRED with exact shape
|
|
282
|
+
* type BlobContext = { filename: string; code: 'INVALID' | 'TOO_LARGE' };
|
|
283
|
+
* const { BlobError, BlobErr } = createTaggedError<'BlobError', BlobContext>('BlobError');
|
|
284
|
+
* BlobError({ message: 'Invalid', context: { filename: 'x', code: 'INVALID' } });
|
|
285
|
+
* // BlobError({ message: 'Error' }); // Type error - context required
|
|
223
286
|
*
|
|
224
|
-
* //
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* const networkError: NetworkError = { name: "NetworkError", message: "Timeout" };
|
|
229
|
-
* const dbError = DatabaseError({
|
|
230
|
-
* message: 'Connection failed',
|
|
231
|
-
* cause: networkError // TypeScript enforces NetworkError type
|
|
232
|
-
* });
|
|
287
|
+
* // Mode 3: Fixed context + cause - context required, cause constrained
|
|
288
|
+
* const { ApiError, ApiErr } = createTaggedError<'ApiError', { endpoint: string }, NetworkError>('ApiError');
|
|
289
|
+
* ApiError({ message: 'Failed', context: { endpoint: '/users' } });
|
|
290
|
+
* ApiError({ message: 'Failed', context: { endpoint: '/users' }, cause: networkError });
|
|
233
291
|
* ```
|
|
234
292
|
*/
|
|
235
|
-
declare function createTaggedError<
|
|
293
|
+
declare function createTaggedError<TName extends `${string}Error`>(name: TName): FlexibleFactories<TName>;
|
|
294
|
+
declare function createTaggedError<TName extends `${string}Error`, TContext extends Record<string, unknown>>(name: TName): ContextFixedFactories<TName, TContext>;
|
|
295
|
+
declare function createTaggedError<TName extends `${string}Error`, TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(name: TName): BothFixedFactories<TName, TContext, TCause>;
|
|
236
296
|
//#endregion
|
|
237
297
|
export { TaggedError, createTaggedError, extractErrorMessage };
|
|
238
298
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/error/index.js
CHANGED
|
@@ -63,58 +63,13 @@ function extractErrorMessage(error) {
|
|
|
63
63
|
}
|
|
64
64
|
return String(error);
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Returns two different factory functions for tagged errors.
|
|
68
|
-
*
|
|
69
|
-
* Given an error name like "NetworkError", this returns:
|
|
70
|
-
* - `NetworkError`: Creates a plain TaggedError object
|
|
71
|
-
* - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
|
|
72
|
-
*
|
|
73
|
-
* The naming pattern automatically replaces the "Error" suffix with "Err" suffix
|
|
74
|
-
* for the Result-wrapped version.
|
|
75
|
-
*
|
|
76
|
-
* @param name - The name of the error type (must end with "Error")
|
|
77
|
-
* @returns An object with two factory functions:
|
|
78
|
-
* - [name]: Function that creates plain TaggedError objects
|
|
79
|
-
* - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* // Simple error without typed cause
|
|
84
|
-
* const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
|
|
85
|
-
*
|
|
86
|
-
* // NetworkError: Creates just the error object
|
|
87
|
-
* const error = NetworkError({
|
|
88
|
-
* message: 'Connection failed',
|
|
89
|
-
* context: { url: 'https://api.example.com' }
|
|
90
|
-
* });
|
|
91
|
-
* // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
|
|
92
|
-
*
|
|
93
|
-
* // NetworkErr: Creates error and wraps in Err result
|
|
94
|
-
* return NetworkErr({
|
|
95
|
-
* message: 'Connection failed',
|
|
96
|
-
* context: { url: 'https://api.example.com' }
|
|
97
|
-
* });
|
|
98
|
-
* // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
|
|
99
|
-
*
|
|
100
|
-
* // Type-safe error chaining with specific cause types
|
|
101
|
-
* type NetworkError = TaggedError<"NetworkError">;
|
|
102
|
-
* const { DatabaseError, DatabaseErr } = createTaggedError<"DatabaseError", NetworkError>('DatabaseError');
|
|
103
|
-
*
|
|
104
|
-
* const networkError: NetworkError = { name: "NetworkError", message: "Timeout" };
|
|
105
|
-
* const dbError = DatabaseError({
|
|
106
|
-
* message: 'Connection failed',
|
|
107
|
-
* cause: networkError // TypeScript enforces NetworkError type
|
|
108
|
-
* });
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
66
|
function createTaggedError(name) {
|
|
112
|
-
const errorConstructor = (
|
|
67
|
+
const errorConstructor = (input) => ({
|
|
113
68
|
name,
|
|
114
|
-
...
|
|
69
|
+
...input
|
|
115
70
|
});
|
|
116
71
|
const errName = name.replace(/Error$/, "Err");
|
|
117
|
-
const errConstructor = (
|
|
72
|
+
const errConstructor = (input) => Err(errorConstructor(input));
|
|
118
73
|
return {
|
|
119
74
|
[name]: errorConstructor,
|
|
120
75
|
[errName]: errConstructor
|
package/dist/error/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["error: unknown","name: TErrorName","error: TaggedErrorWithoutName<TErrorName, TCause>"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type { TaggedError } from \"./types.js\";\nimport { Err } from \"../result/result.js\";\n\n/**\n * Extracts a readable error message from an unknown error value\n *\n * This utility is commonly used in mapErr functions when converting\n * unknown errors to typed error objects in the Result system.\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n *\n * @example\n * ```ts\n * // With native Error\n * const error = new Error(\"Something went wrong\");\n * const message = extractErrorMessage(error); // \"Something went wrong\"\n *\n * // With string error\n * const stringError = \"String error\";\n * const message2 = extractErrorMessage(stringError); // \"String error\"\n *\n * // With object error\n * const unknownError = { code: 500, details: \"Server error\" };\n * const message3 = extractErrorMessage(unknownError); // '{\"code\":500,\"details\":\"Server error\"}'\n *\n * // Used in mapErr function\n * const result = await tryAsync({\n * try: () => riskyOperation(),\n * mapErr: (error) => Err({\n * name: \"NetworkError\",\n * message: extractErrorMessage(error),\n * context: { operation: \"riskyOperation\" },\n * cause: error,\n * }),\n * });\n * ```\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 * Input type for creating a tagged error (everything except the name)\n */\ntype TaggedErrorWithoutName<\n\tTName extends string,\n\tTCause extends TaggedError<string, any> = TaggedError<string, any>,\n> = Omit<TaggedError<TName, TCause>, \"name\">;\n\n/**\n * Replaces the \"Error\" suffix with \"Err\" suffix in error type names.\n *\n * This utility type is used to create companion function names for error constructors\n * that return Err-wrapped results, maintaining consistent naming conventions.\n *\n * @template T - An error type name that must end with \"Error\"\n * @returns The type name with \"Error\" replaced by \"Err\"\n *\n * @example\n * ```ts\n * type NetworkErr = ReplaceErrorWithErr<\"NetworkError\">; // \"NetworkErr\"\n * type ValidationErr = ReplaceErrorWithErr<\"ValidationError\">; // \"ValidationErr\"\n * type AuthErr = ReplaceErrorWithErr<\"AuthError\">; // \"AuthErr\"\n * ```\n */\ntype ReplaceErrorWithErr<T extends `${string}Error`> =\n\tT extends `${infer TBase}Error` ? `${TBase}Err` : never;\n\n/**\n * Return type for createTaggedError - contains both factory functions.\n *\n * Provides two factory functions:\n * - One that creates plain TaggedError objects (named with \"Error\" suffix)\n * - One that creates Err-wrapped TaggedError objects (named with \"Err\" suffix)\n */\ntype TaggedErrorFactories<\n\tTErrorName extends `${string}Error`,\n\tTCause extends TaggedError<string, any> = TaggedError<string, any>,\n> = {\n\t[K in TErrorName]: (\n\t\tinput: TaggedErrorWithoutName<K, TCause>,\n\t) => TaggedError<K, TCause>;\n} & {\n\t[K in ReplaceErrorWithErr<TErrorName>]: (\n\t\tinput: TaggedErrorWithoutName<TErrorName, TCause>,\n\t) => Err<TaggedError<TErrorName, TCause>>;\n};\n\n/**\n * Returns two different factory functions for tagged errors.\n *\n * Given an error name like \"NetworkError\", this returns:\n * - `NetworkError`: Creates a plain TaggedError object\n * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result\n *\n * The naming pattern automatically replaces the \"Error\" suffix with \"Err\" suffix\n * for the Result-wrapped version.\n *\n * @param name - The name of the error type (must end with \"Error\")\n * @returns An object with two factory functions:\n * - [name]: Function that creates plain TaggedError objects\n * - [name with \"Error\" replaced by \"Err\"]: Function that creates Err-wrapped TaggedError objects\n *\n * @example\n * ```ts\n * // Simple error without typed cause\n * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');\n *\n * // NetworkError: Creates just the error object\n * const error = NetworkError({\n * message: 'Connection failed',\n * context: { url: 'https://api.example.com' }\n * });\n * // Returns: { name: 'NetworkError', message: 'Connection failed', ... }\n *\n * // NetworkErr: Creates error and wraps in Err result\n * return NetworkErr({\n * message: 'Connection failed',\n * context: { url: 'https://api.example.com' }\n * });\n * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })\n *\n * // Type-safe error chaining with specific cause types\n * type NetworkError = TaggedError<\"NetworkError\">;\n * const { DatabaseError, DatabaseErr } = createTaggedError<\"DatabaseError\", NetworkError>('DatabaseError');\n *\n * const networkError: NetworkError = { name: \"NetworkError\", message: \"Timeout\" };\n * const dbError = DatabaseError({\n * message: 'Connection failed',\n * cause: networkError // TypeScript enforces NetworkError type\n * });\n * ```\n */\nexport function createTaggedError<\n\tTErrorName extends `${string}Error`,\n\tTCause extends TaggedError<string, any> = TaggedError<string, any>,\n>(name: TErrorName): TaggedErrorFactories<TErrorName, TCause> {\n\tconst errorConstructor = (\n\t\terror: TaggedErrorWithoutName<TErrorName, TCause>,\n\t): TaggedError<TErrorName, TCause> => ({ name, ...error });\n\n\tconst errName = name.replace(\n\t\t/Error$/,\n\t\t\"Err\",\n\t) as ReplaceErrorWithErr<TErrorName>;\n\tconst errConstructor = (error: TaggedErrorWithoutName<TErrorName, TCause>) =>\n\t\tErr(errorConstructor(error));\n\n\treturn {\n\t\t[name]: errorConstructor,\n\t\t[errName]: errConstructor,\n\t} as TaggedErrorFactories<TErrorName, TCause>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FD,SAAgB,kBAGdC,MAA4D;CAC7D,MAAM,mBAAmB,CACxBC,WACsC;EAAE;EAAM,GAAG;CAAO;CAEzD,MAAM,UAAU,KAAK,QACpB,UACA,MACA;CACD,MAAM,iBAAiB,CAACA,UACvB,IAAI,iBAAiB,MAAM,CAAC;AAE7B,QAAO;GACL,OAAO;GACP,UAAU;CACX;AACD"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["error: unknown","name: TName","input: {\n\t\tmessage: string;\n\t\tcontext?: TContext;\n\t\tcause?: TCause;\n\t}"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type { TaggedError } from \"./types.js\";\nimport { Err } from \"../result/result.js\";\n\n/**\n * Extracts a readable error message from an unknown error value\n *\n * This utility is commonly used in mapErr functions when converting\n * unknown errors to typed error objects in the Result system.\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n *\n * @example\n * ```ts\n * // With native Error\n * const error = new Error(\"Something went wrong\");\n * const message = extractErrorMessage(error); // \"Something went wrong\"\n *\n * // With string error\n * const stringError = \"String error\";\n * const message2 = extractErrorMessage(stringError); // \"String error\"\n *\n * // With object error\n * const unknownError = { code: 500, details: \"Server error\" };\n * const message3 = extractErrorMessage(unknownError); // '{\"code\":500,\"details\":\"Server error\"}'\n *\n * // Used in mapErr function\n * const result = await tryAsync({\n * try: () => riskyOperation(),\n * mapErr: (error) => Err({\n * name: \"NetworkError\",\n * message: extractErrorMessage(error),\n * context: { operation: \"riskyOperation\" },\n * cause: error,\n * }),\n * });\n * ```\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 * Base type for any tagged error, used as a constraint for cause parameters.\n */\ntype AnyTaggedError = { name: string; message: string };\n\n/**\n * Replaces the \"Error\" suffix with \"Err\" suffix in error type names.\n *\n * @template T - An error type name that must end with \"Error\"\n * @returns The type name with \"Error\" replaced by \"Err\"\n *\n * @example\n * ```ts\n * type NetworkErr = ReplaceErrorWithErr<\"NetworkError\">; // \"NetworkErr\"\n * type ValidationErr = ReplaceErrorWithErr<\"ValidationError\">; // \"ValidationErr\"\n * ```\n */\ntype ReplaceErrorWithErr<T extends `${string}Error`> =\n\tT extends `${infer TBase}Error` ? `${TBase}Err` : never;\n\n// =============================================================================\n// Factory Return Types\n// =============================================================================\n\n/**\n * Return type when neither context nor cause are constrained.\n * Both factory functions accept any context and cause at call time.\n */\ntype FlexibleFactories<TName extends `${string}Error`> = {\n\t[K in TName]: FlexibleErrorConstructor<K>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: FlexibleErrConstructor<TName>;\n};\n\n/**\n * Return type when context is fixed but cause is flexible.\n * Context shape is locked, but cause can be any TaggedError at call time.\n */\ntype ContextFixedFactories<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown>,\n> = {\n\t[K in TName]: ContextFixedErrorConstructor<K, TContext>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: ContextFixedErrConstructor<TName, TContext>;\n};\n\n/**\n * Return type when both context and cause are fixed.\n * Both shapes are locked at factory creation time.\n */\ntype BothFixedFactories<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = {\n\t[K in TName]: BothFixedErrorConstructor<K, TContext, TCause>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: BothFixedErrConstructor<\n\t\tTName,\n\t\tTContext,\n\t\tTCause\n\t>;\n};\n\n// =============================================================================\n// Flexible Mode Constructor Types (using function overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with flexible context and cause.\n * Uses function overloads to precisely match return types to inputs.\n */\ntype FlexibleErrorConstructor<TName extends string> = {\n\t// Just message - no context or cause\n\t(input: { message: string }): TaggedError<TName, never, never>;\n\t// With context only\n\t<TContext extends Record<string, unknown>>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t}): TaggedError<TName, TContext, never>;\n\t// With cause only\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcause: TCause;\n\t}): TaggedError<TName, never, TCause>;\n\t// With both context and cause\n\t<TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): TaggedError<TName, TContext, TCause>;\n};\n\n/**\n * Creates Err-wrapped TaggedError objects with flexible context and cause.\n */\ntype FlexibleErrConstructor<TName extends string> = {\n\t(input: { message: string }): Err<TaggedError<TName, never, never>>;\n\t<TContext extends Record<string, unknown>>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t}): Err<TaggedError<TName, TContext, never>>;\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, never, TCause>>;\n\t<TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, TContext, TCause>>;\n};\n\n// =============================================================================\n// Context-Fixed Mode Constructor Types\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with fixed context but flexible cause.\n * Context is always required. Cause is optional and inferred at call site.\n */\ntype ContextFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = {\n\t// Without cause\n\t(input: { message: string; context: TContext }): TaggedError<\n\t\tTName,\n\t\tTContext,\n\t\tnever\n\t>;\n\t// With cause\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): TaggedError<TName, TContext, TCause>;\n};\n\n/**\n * Creates Err-wrapped TaggedError objects with fixed context but flexible cause.\n */\ntype ContextFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = {\n\t(input: { message: string; context: TContext }): Err<\n\t\tTaggedError<TName, TContext, never>\n\t>;\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, TContext, TCause>>;\n};\n\n// =============================================================================\n// Both-Fixed Mode Constructor Types\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with both context and cause fixed.\n * Context is required. Cause is optional but must match the specified type.\n */\ntype BothFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = {\n\t// Without cause\n\t(input: { message: string; context: TContext }): TaggedError<\n\t\tTName,\n\t\tTContext,\n\t\tnever\n\t>;\n\t// With cause (must match TCause)\n\t(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): TaggedError<TName, TContext, TCause>;\n};\n\n/**\n * Creates Err-wrapped TaggedError objects with both context and cause fixed.\n */\ntype BothFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = {\n\t(input: { message: string; context: TContext }): Err<\n\t\tTaggedError<TName, TContext, never>\n\t>;\n\t(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, TContext, TCause>>;\n};\n\n// =============================================================================\n// Main Factory Function\n// =============================================================================\n\n/**\n * Creates two factory functions for building tagged errors with type-safe error chaining.\n *\n * Given an error name like \"NetworkError\", this returns:\n * - `NetworkError`: Creates a plain TaggedError object\n * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result\n *\n * **Three usage modes:**\n *\n * 1. **Flexible mode** (no type params): Context and cause are optional, any shape accepted\n * 2. **Fixed context mode** (TContext specified): Context is required with that exact shape\n * 3. **Both fixed mode** (TContext + TCause): Context required, cause (if provided) must match\n *\n * @template TName - The name of the error type (must end with \"Error\")\n * @template TContext - Optional fixed context shape (makes context required)\n * @template TCause - Optional fixed cause type (constrains cause type if provided)\n * @param name - The name of the error type (must end with \"Error\")\n *\n * @example\n * ```ts\n * // Mode 1: Flexible - context optional, any shape\n * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');\n * NetworkError({ message: 'Connection failed' });\n * NetworkError({ message: 'Timeout', context: { url: 'https://...' } });\n *\n * // Mode 2: Fixed context - context REQUIRED with exact shape\n * type BlobContext = { filename: string; code: 'INVALID' | 'TOO_LARGE' };\n * const { BlobError, BlobErr } = createTaggedError<'BlobError', BlobContext>('BlobError');\n * BlobError({ message: 'Invalid', context: { filename: 'x', code: 'INVALID' } });\n * // BlobError({ message: 'Error' }); // Type error - context required\n *\n * // Mode 3: Fixed context + cause - context required, cause constrained\n * const { ApiError, ApiErr } = createTaggedError<'ApiError', { endpoint: string }, NetworkError>('ApiError');\n * ApiError({ message: 'Failed', context: { endpoint: '/users' } });\n * ApiError({ message: 'Failed', context: { endpoint: '/users' }, cause: networkError });\n * ```\n */\n// Overload 1: Flexible (no type constraints)\nexport function createTaggedError<TName extends `${string}Error`>(\n\tname: TName,\n): FlexibleFactories<TName>;\n\n// Overload 2: Context fixed, cause flexible\nexport function createTaggedError<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown>,\n>(name: TName): ContextFixedFactories<TName, TContext>;\n\n// Overload 3: Both context and cause fixed\nexport function createTaggedError<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n>(name: TName): BothFixedFactories<TName, TContext, TCause>;\n\n// Implementation\nexport function createTaggedError<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown> = Record<string, unknown>,\n\tTCause extends AnyTaggedError = never,\n>(name: TName): unknown {\n\tconst errorConstructor = (input: {\n\t\tmessage: string;\n\t\tcontext?: TContext;\n\t\tcause?: TCause;\n\t}) => ({ name, ...input });\n\n\tconst errName = name.replace(/Error$/, \"Err\") as ReplaceErrorWithErr<TName>;\n\tconst errConstructor = (input: {\n\t\tmessage: string;\n\t\tcontext?: TContext;\n\t\tcause?: TCause;\n\t}) => Err(errorConstructor(input));\n\n\treturn {\n\t\t[name]: errorConstructor,\n\t\t[errName]: errConstructor,\n\t};\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,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;AAwQD,SAAgB,kBAIdC,MAAsB;CACvB,MAAM,mBAAmB,CAACC,WAInB;EAAE;EAAM,GAAG;CAAO;CAEzB,MAAM,UAAU,KAAK,QAAQ,UAAU,MAAM;CAC7C,MAAM,iBAAiB,CAACA,UAIlB,IAAI,iBAAiB,MAAM,CAAC;AAElC,QAAO;GACL,OAAO;GACP,UAAU;CACX;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wellcrafted",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Delightful TypeScript patterns for elegant, type-safe applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
"build": "tsdown",
|
|
31
31
|
"format": "biome format --write .",
|
|
32
32
|
"lint": "biome lint --write .",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
33
35
|
"release": "pnpm run build && changeset version && changeset publish"
|
|
34
36
|
},
|
|
35
37
|
"keywords": [
|
|
@@ -46,11 +48,16 @@
|
|
|
46
48
|
],
|
|
47
49
|
"author": "",
|
|
48
50
|
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/wellcrafted-dev/wellcrafted"
|
|
54
|
+
},
|
|
49
55
|
"devDependencies": {
|
|
50
56
|
"@biomejs/biome": "^2.3.3",
|
|
51
57
|
"@changesets/cli": "^2.27.10",
|
|
52
58
|
"@tanstack/query-core": "^5.82.0",
|
|
53
59
|
"tsdown": "^0.12.5",
|
|
54
|
-
"typescript": "^5.8.3"
|
|
60
|
+
"typescript": "^5.8.3",
|
|
61
|
+
"vitest": "^4.0.14"
|
|
55
62
|
}
|
|
56
63
|
}
|