wellcrafted 0.23.1 → 0.24.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 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
- // When catch always returns Ok<T>, function returns Ok<T>
349
- const alwaysSucceeds = trySync({
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({ fallback: "default" }) // Always recover with fallback
360
+ catch: () => Ok([])
352
361
  });
353
- // alwaysSucceeds: Ok<object> - No error checking needed!
354
- console.log(alwaysSucceeds.data); // Safe to access directly
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) => Err(ParseError({ message: "Invalid JSON", cause: 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 Err(ParseError({ message: "Parse failed", cause: error }));
391
+ return ParseErr({ message: "Parse failed", cause: error });
381
392
  }
382
393
  });
383
394
  // smartParse: Result<object, ParseError> - Mixed handling = Result type
@@ -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
- * Error types should follow the convention of ending with "Error" suffix.
14
- * The Err data structure wraps these error types in the Result system.
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 TCause - The type of error that caused this error (default: never = no cause property)
37
+ * @template TContext - Additional context data for the error (default: never = no context 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
- * // Type-safe error chaining with specific cause types
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", never, { host: string; port: number }>;
31
51
  * const networkError: NetworkError = {
32
52
  * name: "NetworkError",
33
- * message: "Socket timeout after 5000ms",
34
- * context: { host: "db.example.com", port: 5432, timeout: 5000 }
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", NetworkError, { operation: string }>;
37
60
  * const dbError: DatabaseError = {
38
61
  * name: "DatabaseError",
39
62
  * message: "Failed to connect to database",
40
- * context: { operation: "connect", retries: 3 },
41
- * cause: networkError // TypeScript enforces this must be 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, TCause extends TaggedError<string, any> = TaggedError<string, any>> = Readonly<{
78
+ type TaggedError<TName extends string = string, TCause = never, TContext = never> = Readonly<{
117
79
  name: TName;
118
80
  message: string;
119
- context?: Record<string, unknown>;
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
- * Input type for creating a tagged error (everything except the name)
121
+ * Base type for any tagged error, used as a constraint for cause parameters.
163
122
  */
164
- type TaggedErrorWithoutName<TName extends string, TCause extends TaggedError<string, any> = TaggedError<string, any>> = Omit<TaggedError<TName, TCause>, "name">;
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 for createTaggedError - contains both factory functions.
184
- *
185
- * Provides two factory functions:
186
- * - One that creates plain TaggedError objects (named with "Error" suffix)
187
- * - One that creates Err-wrapped TaggedError objects (named with "Err" suffix)
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, never, TContext>;
167
+ <TCause extends AnyTaggedError>(input: {
168
+ message: string;
169
+ cause: TCause;
170
+ }): TaggedError<TName, TCause, never>;
171
+ <TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {
172
+ message: string;
173
+ context: TContext;
174
+ cause: TCause;
175
+ }): TaggedError<TName, TCause, TContext>;
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, never, TContext>>;
188
+ <TCause extends AnyTaggedError>(input: {
189
+ message: string;
190
+ cause: TCause;
191
+ }): Err<TaggedError<TName, TCause, never>>;
192
+ <TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(input: {
193
+ message: string;
194
+ context: TContext;
195
+ cause: TCause;
196
+ }): Err<TaggedError<TName, TCause, TContext>>;
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, never, TContext>;
207
+ <TCause extends AnyTaggedError>(input: {
208
+ message: string;
209
+ context: TContext;
210
+ cause: TCause;
211
+ }): TaggedError<TName, TCause, TContext>;
212
+ };
213
+ /**
214
+ * Creates Err-wrapped TaggedError objects with fixed context but flexible cause.
188
215
  */
189
- type TaggedErrorFactories<TErrorName extends `${string}Error`, TCause extends TaggedError<string, any> = TaggedError<string, any>> = { [K in TErrorName]: (input: TaggedErrorWithoutName<K, TCause>) => TaggedError<K, TCause> } & { [K in ReplaceErrorWithErr<TErrorName>]: (input: TaggedErrorWithoutName<TErrorName, TCause>) => Err<TaggedError<TErrorName, TCause>> };
216
+ type ContextFixedErrConstructor<TName extends string, TContext extends Record<string, unknown>> = {
217
+ (input: {
218
+ message: string;
219
+ context: TContext;
220
+ }): Err<TaggedError<TName, never, TContext>>;
221
+ <TCause extends AnyTaggedError>(input: {
222
+ message: string;
223
+ context: TContext;
224
+ cause: TCause;
225
+ }): Err<TaggedError<TName, TCause, TContext>>;
226
+ };
190
227
  /**
191
- * Returns two different factory functions for tagged errors.
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, never, TContext>;
236
+ (input: {
237
+ message: string;
238
+ context: TContext;
239
+ cause: TCause;
240
+ }): TaggedError<TName, TCause, TContext>;
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, never, TContext>>;
250
+ (input: {
251
+ message: string;
252
+ context: TContext;
253
+ cause: TCause;
254
+ }): Err<TaggedError<TName, TCause, TContext>>;
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
- * The naming pattern automatically replaces the "Error" suffix with "Err" suffix
198
- * for the Result-wrapped version.
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
- * // Simple error without typed cause
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
- * // NetworkError: Creates just the error object
211
- * const error = NetworkError({
212
- * message: 'Connection failed',
213
- * context: { url: 'https://api.example.com' }
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
- * // Type-safe error chaining with specific cause types
225
- * type NetworkError = TaggedError<"NetworkError">;
226
- * const { DatabaseError, DatabaseErr } = createTaggedError<"DatabaseError", NetworkError>('DatabaseError');
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<TErrorName extends `${string}Error`, TCause extends TaggedError<string, any> = TaggedError<string, any>>(name: TErrorName): TaggedErrorFactories<TErrorName, TCause>;
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":";;;;;;;AA+GA;;;;;;;;AAGY;;;;AC5EZ;AAkDC;;;;;;;;AAQO;AAAA;;;;AAmBmC;AAAA;;;;;;;;;;;;;;;;;;;;AAmBlC;AAgDT;;;;;;;;AAGyC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KD1E7B,0DAEI,2BAA2B,4BACvC;QACG;;YAEI;UACF;;;;;;AAPT;;;;;;;;AAGY;;;;AC5EZ;AAkDC;;;;;;;;AAQO;AAAA;;;;AAmBmC;AAAA;;;;;;;AAclC,iBA3FO,mBAAA,CA2FP,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA;;;;KApCJ,sBAuCsB,CAAA,cAAA,MAAA,EAAA,eArCX,WAqCW,CAAA,MAAA,EAAA,GAAA,CAAA,GArCgB,WAqChB,CAAA,MAAA,EAAA,GAAA,CAAA,CAAA,GApCvB,IAoCuB,CApClB,WAoCkB,CApCN,KAoCM,EApCC,MAoCD,CAAA,EAAA,MAAA,CAAA;;;;;;;;;AAElB;AAgDT;;;;;;;KApEK,mBAuEgB,CAAA,UAAA,GAAA,MAAA,OAAA,CAAA,GAtEpB,CAsEoB,SAAA,GAAA,KAAA,MAAA,OAAA,GAAA,GAtEiB,KAsEjB,KAAA,GAAA,KAAA;AAAoB;;;;;;;KA7DpC,yEAEW,2BAA2B,oCAEpC,qBACE,uBAAuB,GAAG,YAC7B,YAAY,GAAG,oBAEd,oBAAoB,sBAClB,uBAAuB,YAAY,YACtC,IAAI,YAAY,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDlB,sEAEA,2BAA2B,gCACnC,aAAa,qBAAqB,YAAY"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":"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"}
@@ -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 = (error) => ({
67
+ const errorConstructor = (input) => ({
113
68
  name,
114
- ...error
69
+ ...input
115
70
  });
116
71
  const errName = name.replace(/Error$/, "Err");
117
- const errConstructor = (error) => Err(errorConstructor(error));
72
+ const errConstructor = (input) => Err(errorConstructor(input));
118
73
  return {
119
74
  [name]: errorConstructor,
120
75
  [errName]: errConstructor
@@ -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, never, TContext>;\n\t// With cause only\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcause: TCause;\n\t}): TaggedError<TName, TCause, never>;\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, TCause, TContext>;\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, never, TContext>>;\n\t<TCause extends AnyTaggedError>(input: {\n\t\tmessage: string;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, TCause, never>>;\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, TCause, TContext>>;\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\tnever,\n\t\tTContext\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, TCause, TContext>;\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, never, TContext>\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, TCause, TContext>>;\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\tnever,\n\t\tTContext\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, TCause, TContext>;\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, never, TContext>\n\t>;\n\t(input: {\n\t\tmessage: string;\n\t\tcontext: TContext;\n\t\tcause: TCause;\n\t}): Err<TaggedError<TName, TCause, TContext>>;\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.23.1",
3
+ "version": "0.24.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
  }