wellcrafted 0.21.4 → 0.23.1

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
@@ -62,6 +62,34 @@ const { ApiError, ApiErr } = createTaggedError("ApiError");
62
62
  // ApiError() creates error object, ApiErr() creates Err-wrapped error
63
63
  ```
64
64
 
65
+ ### 🔄 Query Integration
66
+ Seamless TanStack Query integration with dual interfaces
67
+ ```typescript
68
+ import { createQueryFactories } from "wellcrafted/query";
69
+ import { QueryClient } from "@tanstack/query-core";
70
+
71
+ const queryClient = new QueryClient();
72
+ const { defineQuery, defineMutation } = createQueryFactories(queryClient);
73
+
74
+ // Define operations that return Result types
75
+ const userQuery = defineQuery({
76
+ queryKey: ['users', userId],
77
+ resultQueryFn: () => getUserFromAPI(userId) // Returns Result<User, ApiError>
78
+ });
79
+
80
+ // Use reactively in components with automatic state management
81
+ const query = createQuery(userQuery.options());
82
+ // query.data, query.error, query.isPending all managed automatically
83
+
84
+ // Or use imperatively for direct execution (perfect for event handlers)
85
+ const { data, error } = await userQuery.fetch();
86
+ if (error) {
87
+ showErrorToast(error.message);
88
+ return;
89
+ }
90
+ // Use data...
91
+ ```
92
+
65
93
  ## Installation
66
94
 
67
95
  ```bash
@@ -81,7 +109,7 @@ type ApiError = ReturnType<typeof ApiError>;
81
109
  // Wrap any throwing operation
82
110
  const { data, error } = await tryAsync({
83
111
  try: () => fetch('/api/user').then(r => r.json()),
84
- mapErr: (error) => ApiErr({
112
+ catch: (error) => ApiErr({
85
113
  message: "Failed to fetch user",
86
114
  context: { endpoint: '/api/user' },
87
115
  cause: error
@@ -145,13 +173,12 @@ Mix and match utilities
145
173
  The Result type makes error handling explicit and type-safe:
146
174
 
147
175
  ```typescript
148
- // The entire implementation
149
176
  type Ok<T> = { data: T; error: null };
150
177
  type Err<E> = { error: E; data: null };
151
178
  type Result<T, E> = Ok<T> | Err<E>;
152
179
  ```
153
180
 
154
- **The Magic**: This creates a discriminated union where TypeScript automatically narrows types:
181
+ This creates a discriminated union where TypeScript automatically narrows types:
155
182
 
156
183
  ```typescript
157
184
  if (result.error) {
@@ -182,7 +209,7 @@ if (error) {
182
209
  // Synchronous
183
210
  const result = trySync({
184
211
  try: () => JSON.parse(jsonString),
185
- mapErr: (error) => Err({
212
+ catch: (error) => Err({
186
213
  name: "ParseError",
187
214
  message: "Invalid JSON",
188
215
  context: { input: jsonString },
@@ -193,7 +220,7 @@ const result = trySync({
193
220
  // Asynchronous
194
221
  const result = await tryAsync({
195
222
  try: () => fetch(url),
196
- mapErr: (error) => Err({
223
+ catch: (error) => Err({
197
224
  name: "NetworkError",
198
225
  message: "Request failed",
199
226
  context: { url },
@@ -202,62 +229,162 @@ const result = await tryAsync({
202
229
  });
203
230
  ```
204
231
 
205
- ### Service Layer Example
232
+ ### Real-World Service + Query Layer Example
206
233
 
207
234
  ```typescript
208
- import { Result, Ok, tryAsync } from "wellcrafted/result";
235
+ // 1. Service Layer - Pure business logic
209
236
  import { createTaggedError } from "wellcrafted/error";
237
+ import { tryAsync, Result } from "wellcrafted/result";
210
238
 
211
- // Define service-specific errors
212
- const { ValidationError, ValidationErr } = createTaggedError("ValidationError");
213
- const { DatabaseError, DatabaseErr } = createTaggedError("DatabaseError");
239
+ const { RecorderServiceError, RecorderServiceErr } = createTaggedError("RecorderServiceError");
240
+ type RecorderServiceError = ReturnType<typeof RecorderServiceError>;
214
241
 
215
- type ValidationError = ReturnType<typeof ValidationError>;
216
- type DatabaseError = ReturnType<typeof DatabaseError>;
242
+ export function createRecorderService() {
243
+ let isRecording = false;
244
+ let currentBlob: Blob | null = null;
217
245
 
218
- // Factory function pattern - no classes!
219
- export function createUserService(db: Database) {
220
246
  return {
221
- async createUser(input: CreateUserInput): Promise<Result<User, ValidationError | DatabaseError>> {
222
- // Direct return with Err variant
223
- if (!input.email.includes('@')) {
224
- return ValidationErr({
225
- message: "Invalid email format",
226
- context: { field: 'email', value: input.email },
247
+ async startRecording(): Promise<Result<void, RecorderServiceError>> {
248
+ if (isRecording) {
249
+ return RecorderServiceErr({
250
+ message: "Already recording",
251
+ context: { currentState: 'recording' },
227
252
  cause: undefined
228
253
  });
229
254
  }
230
255
 
231
256
  return tryAsync({
232
- try: () => db.save(input),
233
- mapErr: (error) => DatabaseErr({
234
- message: "Failed to save user",
235
- context: { operation: 'createUser', input },
257
+ try: async () => {
258
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
259
+ const recorder = new MediaRecorder(stream);
260
+ // ... recording setup
261
+ isRecording = true;
262
+ },
263
+ catch: (error) => RecorderServiceErr({
264
+ message: "Failed to start recording",
265
+ context: { permissions: 'microphone' },
236
266
  cause: error
237
267
  })
238
268
  });
239
269
  },
240
270
 
241
- async getUser(id: string): Promise<Result<User | null, DatabaseError>> {
242
- return tryAsync({
243
- try: () => db.findById(id),
244
- mapErr: (error) => DatabaseErr({
245
- message: "Failed to fetch user",
246
- context: { userId: id },
247
- cause: error
248
- })
249
- });
271
+ async stopRecording(): Promise<Result<Blob, RecorderServiceError>> {
272
+ if (!isRecording) {
273
+ return RecorderServiceErr({
274
+ message: "Not currently recording",
275
+ context: { currentState: 'idle' },
276
+ cause: undefined
277
+ });
278
+ }
279
+
280
+ // Stop recording and return blob...
281
+ isRecording = false;
282
+ return Ok(currentBlob!);
250
283
  }
251
284
  };
252
285
  }
253
286
 
254
- // Export type for the service
255
- export type UserService = ReturnType<typeof createUserService>;
287
+ // 2. Query Layer - Adds caching, reactivity, and UI error handling
288
+ import { createQueryFactories } from "wellcrafted/query";
289
+
290
+ const { defineQuery, defineMutation } = createQueryFactories(queryClient);
291
+
292
+ export const recorder = {
293
+ getRecorderState: defineQuery({
294
+ queryKey: ['recorder', 'state'],
295
+ resultQueryFn: async () => {
296
+ const { data, error } = await services.recorder.getState();
297
+ if (error) {
298
+ // Transform service error to UI-friendly error
299
+ return Err({
300
+ title: "❌ Failed to get recorder state",
301
+ description: error.message,
302
+ action: { type: 'retry' }
303
+ });
304
+ }
305
+ return Ok(data);
306
+ },
307
+ refetchInterval: 1000, // Poll for state changes
308
+ }),
309
+
310
+ startRecording: defineMutation({
311
+ mutationKey: ['recorder', 'start'],
312
+ resultMutationFn: async () => {
313
+ const { error } = await services.recorder.startRecording();
314
+ if (error) {
315
+ return Err({
316
+ title: "❌ Failed to start recording",
317
+ description: error.message,
318
+ action: { type: 'more-details', error }
319
+ });
320
+ }
321
+
322
+ // Optimistically update cache
323
+ queryClient.setQueryData(['recorder', 'state'], 'recording');
324
+ return Ok(undefined);
325
+ }
326
+ })
327
+ };
328
+
329
+ // 3. Component Usage - Choose reactive or imperative based on needs
330
+ // Reactive: Automatic state management
331
+ const recorderState = createQuery(recorder.getRecorderState.options());
332
+
333
+ // Imperative: Direct execution for event handlers
334
+ async function handleStartRecording() {
335
+ const { error } = await recorder.startRecording.execute();
336
+ if (error) {
337
+ showToast(error.title, { description: error.description });
338
+ }
339
+ }
340
+ ```
341
+
342
+ ## Smart Return Type Narrowing
343
+
344
+ The `catch` parameter in `trySync` and `tryAsync` enables smart return type narrowing based on your error handling strategy:
345
+
346
+ ### Recovery Pattern (Always Succeeds)
347
+ ```typescript
348
+ // When catch always returns Ok<T>, function returns Ok<T>
349
+ const alwaysSucceeds = trySync({
350
+ try: () => JSON.parse(riskyJson),
351
+ catch: () => Ok({ fallback: "default" }) // Always recover with fallback
352
+ });
353
+ // alwaysSucceeds: Ok<object> - No error checking needed!
354
+ console.log(alwaysSucceeds.data); // Safe to access directly
355
+ ```
356
+
357
+ ### Propagation Pattern (May Fail)
358
+ ```typescript
359
+ // When catch can return Err<E>, function returns Result<T, E>
360
+ const mayFail = trySync({
361
+ try: () => JSON.parse(riskyJson),
362
+ catch: (error) => Err(ParseError({ message: "Invalid JSON", cause: error }))
363
+ });
364
+ // mayFail: Result<object, ParseError> - Must check for errors
365
+ if (isOk(mayFail)) {
366
+ console.log(mayFail.data); // Only safe after checking
367
+ }
368
+ ```
256
369
 
257
- // Create a live instance (dependency injection at build time)
258
- export const UserServiceLive = createUserService(databaseInstance);
370
+ ### Mixed Strategy (Conditional Recovery)
371
+ ```typescript
372
+ const smartParse = trySync({
373
+ try: () => JSON.parse(input),
374
+ catch: (error) => {
375
+ // Recover from empty input
376
+ if (input.trim() === "") {
377
+ return Ok({}); // Return Ok<T> for fallback
378
+ }
379
+ // Propagate other errors
380
+ return Err(ParseError({ message: "Parse failed", cause: error }));
381
+ }
382
+ });
383
+ // smartParse: Result<object, ParseError> - Mixed handling = Result type
259
384
  ```
260
385
 
386
+ This eliminates unnecessary error checking when you always recover, while still requiring proper error handling when failures are possible.
387
+
261
388
  ## Why wellcrafted?
262
389
 
263
390
  JavaScript's `try-catch` has fundamental problems:
@@ -447,11 +574,16 @@ function useUser(id: number) {
447
574
 
448
575
  | | wellcrafted | fp-ts | Effect | neverthrow |
449
576
  |---|---|---|---|---|
577
+ | **Bundle Size** | < 2KB | ~30KB | ~50KB | ~5KB |
450
578
  | **Learning Curve** | Minimal | Steep | Steep | Moderate |
451
579
  | **Syntax** | Native async/await | Pipe operators | Generators | Method chains |
452
- | **Bundle Size** | < 2KB | ~30KB | ~50KB | ~5KB |
453
580
  | **Type Safety** | ✅ Full | ✅ Full | ✅ Full | ✅ Full |
454
581
  | **Serializable Errors** | ✅ Built-in | ❌ Classes | ❌ Classes | ❌ Classes |
582
+ | **Runtime Overhead** | Zero | Minimal | Moderate | Minimal |
583
+
584
+ ## Advanced Usage
585
+
586
+ For comprehensive examples, service layer patterns, framework integrations, and migration guides, see the **[full documentation →](https://docs.wellcrafted.dev)**
455
587
 
456
588
  ## API Reference
457
589
 
@@ -462,18 +594,32 @@ function useUser(id: number) {
462
594
  - **`isErr(result)`** - Type guard for failure
463
595
  - **`trySync(options)`** - Wrap throwing function
464
596
  - **`tryAsync(options)`** - Wrap async function
597
+ - **`unwrap(result)`** - Extract data or throw error
598
+ - **`resolve(value)`** - Handle values that may or may not be Results
465
599
  - **`partitionResults(results)`** - Split array into oks/errs
466
600
 
601
+ ### Query Functions
602
+ - **`createQueryFactories(client)`** - Create query/mutation factories for TanStack Query
603
+ - **`defineQuery(options)`** - Define a query with dual interface (`.options()` + `.fetch()`)
604
+ - **`defineMutation(options)`** - Define a mutation with dual interface (`.options()` + `.execute()`)
605
+
467
606
  ### Error Functions
468
607
  - **`createTaggedError(name)`** - Creates error factory functions
469
608
  - Returns two functions: `{ErrorName}` and `{ErrorName}Err`
470
609
  - The first creates plain error objects
471
610
  - The second creates Err-wrapped errors
611
+ - **`extractErrorMessage(error)`** - Extract readable message from unknown error
472
612
 
473
613
  ### Types
474
614
  - **`Result<T, E>`** - Union of Ok<T> | Err<E>
615
+ - **`Ok<T>`** - Success result type
616
+ - **`Err<E>`** - Error result type
475
617
  - **`TaggedError<T>`** - Structured error type
476
618
  - **`Brand<T, B>`** - Branded type wrapper
619
+ - **`ExtractOkFromResult<R>`** - Extract Ok variant from Result union
620
+ - **`ExtractErrFromResult<R>`** - Extract Err variant from Result union
621
+ - **`UnwrapOk<R>`** - Extract success value type from Result
622
+ - **`UnwrapErr<R>`** - Extract error value type from Result
477
623
 
478
624
  ## License
479
625
 
@@ -1,41 +1,85 @@
1
- import { Err } from "../result-DucAuR8u.js";
1
+ import { Err } from "../result-DRq8PMRe.js";
2
2
 
3
3
  //#region src/error/types.d.ts
4
4
 
5
- /**
6
- * Base error structure for all errors in the Result system.
7
- *
8
- * Provides a consistent structure for error objects that are JSON-serializable
9
- * and contain comprehensive debugging information.
10
- *
11
- * @example
12
- * ```ts
13
- * const error: BaseError = {
14
- * name: "DatabaseError",
15
- * message: "Connection failed",
16
- * context: { host: "localhost", port: 5432 },
17
- * cause: originalError
18
- * };
19
- * ```
20
- */
21
- type BaseError = Readonly<{
22
- name: string;
23
- message: string;
24
- context?: Record<string, unknown>;
25
- cause: unknown;
26
- }>;
27
5
  /**
28
6
  * Creates a tagged error type for type-safe error handling.
29
7
  * Uses the `name` property as a discriminator for tagged unions.
30
8
  *
9
+ * The `cause` property enables error chaining, creating a JSON-serializable
10
+ * call stack. Each error wraps its cause, building a complete trace of how
11
+ * an error propagated through your application layers.
12
+ *
31
13
  * Error types should follow the convention of ending with "Error" suffix.
32
14
  * The Err data structure wraps these error types in the Result system.
33
15
  *
34
16
  * @example
35
17
  * ```ts
36
- * type ValidationError = TaggedError<"ValidationError">
37
- * type NetworkError = TaggedError<"NetworkError">
18
+ * // Simple error without cause
19
+ * type ValidationError = TaggedError<"ValidationError">;
20
+ * const validationError: ValidationError = {
21
+ * name: "ValidationError",
22
+ * message: "Input is required"
23
+ * };
38
24
  *
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
+ *
31
+ * const networkError: NetworkError = {
32
+ * name: "NetworkError",
33
+ * message: "Socket timeout after 5000ms",
34
+ * context: { host: "db.example.com", port: 5432, timeout: 5000 }
35
+ * };
36
+ *
37
+ * const dbError: DatabaseError = {
38
+ * name: "DatabaseError",
39
+ * message: "Failed to connect to database",
40
+ * context: { operation: "connect", retries: 3 },
41
+ * cause: networkError // TypeScript enforces this must be NetworkError
42
+ * };
43
+ *
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
+ * // Discriminated unions still work
39
83
  * function handleError(error: ValidationError | NetworkError) {
40
84
  * switch (error.name) {
41
85
  * case "ValidationError": // TypeScript knows this is ValidationError
@@ -51,8 +95,7 @@ type BaseError = Readonly<{
51
95
  * return Err({
52
96
  * name: "ValidationError",
53
97
  * message: "Input is required",
54
- * context: { input },
55
- * cause: null,
98
+ * context: { input }
56
99
  * });
57
100
  * }
58
101
  * return Ok(input);
@@ -63,17 +106,19 @@ type BaseError = Readonly<{
63
106
  * if (!isAuthenticated) {
64
107
  * return Err({
65
108
  * name: "AuthError",
66
- * message: "User not authenticated",
67
- * cause: null,
109
+ * message: "User not authenticated"
68
110
  * });
69
111
  * }
70
112
  * return Ok(currentUser);
71
113
  * }
72
114
  * ```
73
115
  */
74
- type TaggedError<T extends string> = BaseError & {
75
- readonly name: T;
76
- };
116
+ type TaggedError<TName extends string = string, TCause extends TaggedError<string, any> = TaggedError<string, any>> = Readonly<{
117
+ name: TName;
118
+ message: string;
119
+ context?: Record<string, unknown>;
120
+ cause?: TCause;
121
+ }>;
77
122
  //# sourceMappingURL=types.d.ts.map
78
123
  //#endregion
79
124
  //#region src/error/utils.d.ts
@@ -116,7 +161,7 @@ declare function extractErrorMessage(error: unknown): string;
116
161
  /**
117
162
  * Input type for creating a tagged error (everything except the name)
118
163
  */
119
- type TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, "name">;
164
+ type TaggedErrorWithoutName<TName extends string, TCause extends TaggedError<string, any> = TaggedError<string, any>> = Omit<TaggedError<TName, TCause>, "name">;
120
165
  /**
121
166
  * Replaces the "Error" suffix with "Err" suffix in error type names.
122
167
  *
@@ -141,7 +186,7 @@ type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}
141
186
  * - One that creates plain TaggedError objects (named with "Error" suffix)
142
187
  * - One that creates Err-wrapped TaggedError objects (named with "Err" suffix)
143
188
  */
144
- type TaggedErrorFactories<TErrorName extends `${string}Error`> = { [K in TErrorName]: (input: TaggedErrorWithoutName<K>) => TaggedError<K> } & { [K in ReplaceErrorWithErr<TErrorName>]: (input: TaggedErrorWithoutName<TErrorName>) => Err<TaggedError<TErrorName>> };
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>> };
145
190
  /**
146
191
  * Returns two different factory functions for tagged errors.
147
192
  *
@@ -159,26 +204,35 @@ type TaggedErrorFactories<TErrorName extends `${string}Error`> = { [K in TErrorN
159
204
  *
160
205
  * @example
161
206
  * ```ts
207
+ * // Simple error without typed cause
162
208
  * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
163
209
  *
164
210
  * // NetworkError: Creates just the error object
165
211
  * const error = NetworkError({
166
212
  * message: 'Connection failed',
167
- * context: { url: 'https://api.example.com' },
168
- * cause: undefined
213
+ * context: { url: 'https://api.example.com' }
169
214
  * });
170
215
  * // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
171
216
  *
172
217
  * // NetworkErr: Creates error and wraps in Err result
173
218
  * return NetworkErr({
174
219
  * message: 'Connection failed',
175
- * context: { url: 'https://api.example.com' },
176
- * cause: undefined
220
+ * context: { url: 'https://api.example.com' }
177
221
  * });
178
222
  * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
223
+ *
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
+ * });
179
233
  * ```
180
234
  */
181
- declare function createTaggedError<TErrorName extends `${string}Error`>(name: TErrorName): TaggedErrorFactories<TErrorName>;
235
+ declare function createTaggedError<TErrorName extends `${string}Error`, TCause extends TaggedError<string, any> = TaggedError<string, any>>(name: TErrorName): TaggedErrorFactories<TErrorName, TCause>;
182
236
  //#endregion
183
- export { BaseError, TaggedError, createTaggedError, extractErrorMessage };
237
+ export { TaggedError, createTaggedError, extractErrorMessage };
184
238
  //# 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":";;;;;;;AAgBA;;;;AAAgC;AAsDhC;;;;AACiB;;;;ACjCD,KDtBJ,SAAA,GAAY,QCsBW,CAAA;EAoC9B,IAAA,EAAA,MAAA;EAAsB,OAAA,EAAA,MAAA;EAAA,OAAsC,CAAA,EDvDtD,MCuDsD,CAAA,MAAA,EAAA,OAAA,CAAA;EAAC,KAAb,EAAA,OAAA;CAAW,CAAA;AAAZ;AAAA;;;;AAmBT;AAAA;;;;;;;;;;;;;;AAclC;AAuCT;;;;;AAEuB;;;;;;;;;;;;;;;;;;;;;KD9EX,gCAAgC;iBAC5B;;;;;;AAvDhB;;;;AAAgC;AAsDhC;;;;AACiB;;;;ACjCjB;AA+BC;;;;;AAKmD;AAAA;;;;AAmBT;AAAA;;;;;;;;;AAaX,iBApEhB,mBAAA,CAoEgB,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA;;;;KAhC3B,sBAiCC,CAAA,UAAA,MAAA,CAAA,GAjC0C,IAiC1C,CAjC+C,WAiC/C,CAjC2D,CAiC3D,CAAA,EAAA,MAAA,CAAA;AAAG;AAuCT;;;;;AAEuB;;;;;;;;;;KAxDlB,kDACJ,qCAAqC;;;;;;;;KASjC,oEACE,qBAAqB,uBAAuB,OAAO,YAAY,eAE/D,oBAAoB,sBAClB,uBAAuB,gBAC1B,IAAI,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuCN,6DACT,aACJ,qBAAqB"}
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,4 +1,4 @@
1
- import { Err } from "../result-BPrctj1f.js";
1
+ import { Err } from "../result-B1iWFqM9.js";
2
2
 
3
3
  //#region src/error/utils.ts
4
4
  /**
@@ -39,11 +39,22 @@ import { Err } from "../result-BPrctj1f.js";
39
39
  function extractErrorMessage(error) {
40
40
  if (error instanceof Error) return error.message;
41
41
  if (typeof error === "string") return error;
42
- if (typeof error === "object" && error !== null) {
42
+ if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
43
+ if (typeof error === "symbol") return error.toString();
44
+ if (error === null) return "null";
45
+ if (error === void 0) return "undefined";
46
+ if (Array.isArray(error)) return JSON.stringify(error);
47
+ if (typeof error === "object") {
43
48
  const errorObj = error;
44
- if (typeof errorObj.message === "string") return errorObj.message;
45
- if (typeof errorObj.error === "string") return errorObj.error;
46
- if (typeof errorObj.description === "string") return errorObj.description;
49
+ const messageProps = [
50
+ "message",
51
+ "error",
52
+ "description",
53
+ "title",
54
+ "reason",
55
+ "details"
56
+ ];
57
+ for (const prop of messageProps) if (prop in errorObj && typeof errorObj[prop] === "string") return errorObj[prop];
47
58
  try {
48
59
  return JSON.stringify(error);
49
60
  } catch {
@@ -69,29 +80,38 @@ function extractErrorMessage(error) {
69
80
  *
70
81
  * @example
71
82
  * ```ts
83
+ * // Simple error without typed cause
72
84
  * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
73
85
  *
74
86
  * // NetworkError: Creates just the error object
75
87
  * const error = NetworkError({
76
88
  * message: 'Connection failed',
77
- * context: { url: 'https://api.example.com' },
78
- * cause: undefined
89
+ * context: { url: 'https://api.example.com' }
79
90
  * });
80
91
  * // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
81
92
  *
82
93
  * // NetworkErr: Creates error and wraps in Err result
83
94
  * return NetworkErr({
84
95
  * message: 'Connection failed',
85
- * context: { url: 'https://api.example.com' },
86
- * cause: undefined
96
+ * context: { url: 'https://api.example.com' }
87
97
  * });
88
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
+ * });
89
109
  * ```
90
110
  */
91
111
  function createTaggedError(name) {
92
112
  const errorConstructor = (error) => ({
93
- ...error,
94
- name
113
+ name,
114
+ ...error
95
115
  });
96
116
  const errName = name.replace(/Error$/, "Err");
97
117
  const errConstructor = (error) => Err(errorConstructor(error));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["error: unknown","name: TErrorName","error: TaggedErrorWithoutName<TErrorName>"],"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\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"object\" && error !== null) {\n\t\t// Check for common error properties\n\t\tconst errorObj = error as Record<string, unknown>;\n\t\tif (typeof errorObj.message === \"string\") {\n\t\t\treturn errorObj.message;\n\t\t}\n\t\tif (typeof errorObj.error === \"string\") {\n\t\t\treturn errorObj.error;\n\t\t}\n\t\tif (typeof errorObj.description === \"string\") {\n\t\t\treturn errorObj.description;\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\treturn String(error);\n}\n\n/**\n * Input type for creating a tagged error (everything except the name)\n */\ntype TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, \"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<TErrorName extends `${string}Error`> = {\n\t[K in TErrorName]: (input: TaggedErrorWithoutName<K>) => TaggedError<K>;\n} & {\n\t[K in ReplaceErrorWithErr<TErrorName>]: (\n\t\tinput: TaggedErrorWithoutName<TErrorName>,\n\t) => Err<TaggedError<TErrorName>>;\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 * 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 * cause: undefined\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 * cause: undefined\n * });\n * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })\n * ```\n */\nexport function createTaggedError<TErrorName extends `${string}Error`>(\n\tname: TErrorName,\n): TaggedErrorFactories<TErrorName> {\n\tconst errorConstructor = (\n\t\terror: TaggedErrorWithoutName<TErrorName>,\n\t): TaggedError<TErrorName> => ({ ...error, name }) as TaggedError<TErrorName>;\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>) =>\n\t\tErr(errorConstructor(error));\n\n\treturn {\n\t\t[name]: errorConstructor,\n\t\t[errName]: errConstructor,\n\t} as TaggedErrorFactories<TErrorName>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,oBAAoBA,OAAwB;AAC3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAGd,YAAW,UAAU,SACpB,QAAO;AAGR,YAAW,UAAU,YAAY,UAAU,MAAM;EAEhD,MAAM,WAAW;AACjB,aAAW,SAAS,YAAY,SAC/B,QAAO,SAAS;AAEjB,aAAW,SAAS,UAAU,SAC7B,QAAO,SAAS;AAEjB,aAAW,SAAS,gBAAgB,SACnC,QAAO,SAAS;AAIjB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAED,QAAO,OAAO,MAAM;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ED,SAAgB,kBACfC,MACmC;CACnC,MAAM,mBAAmB,CACxBC,WAC8B;EAAE,GAAG;EAAO;CAAM;CAEjD,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: 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"}