wellcrafted 0.22.0 → 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
@@ -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,173 @@ 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
+ }
256
321
 
257
- // Create a live instance (dependency injection at build time)
258
- export const UserServiceLive = createUserService(databaseInstance);
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
+ // ❌ 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({
359
+ try: () => JSON.parse(riskyJson),
360
+ catch: () => Ok([])
361
+ });
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
259
366
  ```
260
367
 
368
+ ### Propagation Pattern (May Fail)
369
+ ```typescript
370
+ // When catch can return Err<E>, function returns Result<T, E>
371
+ const mayFail = trySync({
372
+ try: () => JSON.parse(riskyJson),
373
+ catch: (error) => ParseErr({ message: "Invalid JSON", cause: error })
374
+ });
375
+ // mayFail: Result<object, ParseError> - Must check for errors
376
+ if (isOk(mayFail)) {
377
+ console.log(mayFail.data); // Only safe after checking
378
+ }
379
+ ```
380
+
381
+ ### Mixed Strategy (Conditional Recovery)
382
+ ```typescript
383
+ const smartParse = trySync({
384
+ try: () => JSON.parse(input),
385
+ catch: (error) => {
386
+ // Recover from empty input
387
+ if (input.trim() === "") {
388
+ return Ok({}); // Return Ok<T> for fallback
389
+ }
390
+ // Propagate other errors
391
+ return ParseErr({ message: "Parse failed", cause: error });
392
+ }
393
+ });
394
+ // smartParse: Result<object, ParseError> - Mixed handling = Result type
395
+ ```
396
+
397
+ This eliminates unnecessary error checking when you always recover, while still requiring proper error handling when failures are possible.
398
+
261
399
  ## Why wellcrafted?
262
400
 
263
401
  JavaScript's `try-catch` has fundamental problems:
@@ -447,11 +585,16 @@ function useUser(id: number) {
447
585
 
448
586
  | | wellcrafted | fp-ts | Effect | neverthrow |
449
587
  |---|---|---|---|---|
588
+ | **Bundle Size** | < 2KB | ~30KB | ~50KB | ~5KB |
450
589
  | **Learning Curve** | Minimal | Steep | Steep | Moderate |
451
590
  | **Syntax** | Native async/await | Pipe operators | Generators | Method chains |
452
- | **Bundle Size** | < 2KB | ~30KB | ~50KB | ~5KB |
453
591
  | **Type Safety** | ✅ Full | ✅ Full | ✅ Full | ✅ Full |
454
592
  | **Serializable Errors** | ✅ Built-in | ❌ Classes | ❌ Classes | ❌ Classes |
593
+ | **Runtime Overhead** | Zero | Minimal | Moderate | Minimal |
594
+
595
+ ## Advanced Usage
596
+
597
+ For comprehensive examples, service layer patterns, framework integrations, and migration guides, see the **[full documentation →](https://docs.wellcrafted.dev)**
455
598
 
456
599
  ## API Reference
457
600
 
@@ -462,18 +605,32 @@ function useUser(id: number) {
462
605
  - **`isErr(result)`** - Type guard for failure
463
606
  - **`trySync(options)`** - Wrap throwing function
464
607
  - **`tryAsync(options)`** - Wrap async function
608
+ - **`unwrap(result)`** - Extract data or throw error
609
+ - **`resolve(value)`** - Handle values that may or may not be Results
465
610
  - **`partitionResults(results)`** - Split array into oks/errs
466
611
 
612
+ ### Query Functions
613
+ - **`createQueryFactories(client)`** - Create query/mutation factories for TanStack Query
614
+ - **`defineQuery(options)`** - Define a query with dual interface (`.options()` + `.fetch()`)
615
+ - **`defineMutation(options)`** - Define a mutation with dual interface (`.options()` + `.execute()`)
616
+
467
617
  ### Error Functions
468
618
  - **`createTaggedError(name)`** - Creates error factory functions
469
619
  - Returns two functions: `{ErrorName}` and `{ErrorName}Err`
470
620
  - The first creates plain error objects
471
621
  - The second creates Err-wrapped errors
622
+ - **`extractErrorMessage(error)`** - Extract readable message from unknown error
472
623
 
473
624
  ### Types
474
625
  - **`Result<T, E>`** - Union of Ok<T> | Err<E>
626
+ - **`Ok<T>`** - Success result type
627
+ - **`Err<E>`** - Error result type
475
628
  - **`TaggedError<T>`** - Structured error type
476
629
  - **`Brand<T, B>`** - Branded type wrapper
630
+ - **`ExtractOkFromResult<R>`** - Extract Ok variant from Result union
631
+ - **`ExtractErrFromResult<R>`** - Extract Err variant from Result union
632
+ - **`UnwrapOk<R>`** - Extract success value type from Result
633
+ - **`UnwrapErr<R>`** - Extract error value type from Result
477
634
 
478
635
  ## License
479
636
 
@@ -3,39 +3,68 @@ import { Err } from "../result-DRq8PMRe.js";
3
3
  //#region src/error/types.d.ts
4
4
 
5
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
- * ```
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).
20
9
  */
21
- type BaseError = Readonly<{
22
- name: string;
23
- message: string;
24
- context?: Record<string, unknown>;
25
- cause: unknown;
26
- }>;
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
+ };
27
21
  /**
28
22
  * Creates a tagged error type for type-safe error handling.
29
23
  * Uses the `name` property as a discriminator for tagged unions.
30
24
  *
31
- * Error types should follow the convention of ending with "Error" suffix.
32
- * The Err data structure wraps these error types in the Result system.
25
+ * The `cause` property enables error chaining, creating a JSON-serializable
26
+ * call stack. Each error wraps its cause, building a complete trace of how
27
+ * an error propagated through your application layers.
28
+ *
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)
33
38
  *
34
39
  * @example
35
40
  * ```ts
36
- * type ValidationError = TaggedError<"ValidationError">
37
- * type NetworkError = TaggedError<"NetworkError">
41
+ * // Simple error without context or cause (properties don't exist)
42
+ * type ValidationError = TaggedError<"ValidationError">;
43
+ * const validationError: ValidationError = {
44
+ * name: "ValidationError",
45
+ * message: "Input is required"
46
+ * };
47
+ * // validationError.context // Property 'context' does not exist
48
+ *
49
+ * // Error with required context
50
+ * type NetworkError = TaggedError<"NetworkError", never, { host: string; port: number }>;
51
+ * const networkError: NetworkError = {
52
+ * name: "NetworkError",
53
+ * message: "Socket timeout",
54
+ * context: { host: "db.example.com", port: 5432 } // Required!
55
+ * };
56
+ * const host = networkError.context.host; // No optional chaining needed
57
+ *
58
+ * // Type-safe error chaining with required cause
59
+ * type DatabaseError = TaggedError<"DatabaseError", NetworkError, { operation: string }>;
60
+ * const dbError: DatabaseError = {
61
+ * name: "DatabaseError",
62
+ * message: "Failed to connect to database",
63
+ * context: { operation: "connect" }, // Required!
64
+ * cause: networkError // Required!
65
+ * };
38
66
  *
67
+ * // Discriminated unions still work
39
68
  * function handleError(error: ValidationError | NetworkError) {
40
69
  * switch (error.name) {
41
70
  * case "ValidationError": // TypeScript knows this is ValidationError
@@ -44,37 +73,12 @@ type BaseError = Readonly<{
44
73
  * break;
45
74
  * }
46
75
  * }
47
- *
48
- * // Used in Result types:
49
- * function validate(input: string): Result<string, ValidationError> {
50
- * if (!input) {
51
- * return Err({
52
- * name: "ValidationError",
53
- * message: "Input is required",
54
- * context: { input },
55
- * cause: null,
56
- * });
57
- * }
58
- * return Ok(input);
59
- * }
60
- *
61
- * // Context is optional - omit when not needed:
62
- * function checkAuth(): Result<User, AuthError> {
63
- * if (!isAuthenticated) {
64
- * return Err({
65
- * name: "AuthError",
66
- * message: "User not authenticated",
67
- * cause: null,
68
- * });
69
- * }
70
- * return Ok(currentUser);
71
- * }
72
76
  * ```
73
77
  */
74
- type TaggedError<T extends string> = BaseError & {
75
- readonly name: T;
76
- };
77
- //# sourceMappingURL=types.d.ts.map
78
+ type TaggedError<TName extends string = string, TCause = never, TContext = never> = Readonly<{
79
+ name: TName;
80
+ message: string;
81
+ } & WithContext<TContext> & WithCause<TCause>>;
78
82
  //#endregion
79
83
  //#region src/error/utils.d.ts
80
84
  /**
@@ -114,15 +118,15 @@ type TaggedError<T extends string> = BaseError & {
114
118
  */
115
119
  declare function extractErrorMessage(error: unknown): string;
116
120
  /**
117
- * 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.
118
122
  */
119
- type TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, "name">;
123
+ type AnyTaggedError = {
124
+ name: string;
125
+ message: string;
126
+ };
120
127
  /**
121
128
  * Replaces the "Error" suffix with "Err" suffix in error type names.
122
129
  *
123
- * This utility type is used to create companion function names for error constructors
124
- * that return Err-wrapped results, maintaining consistent naming conventions.
125
- *
126
130
  * @template T - An error type name that must end with "Error"
127
131
  * @returns The type name with "Error" replaced by "Err"
128
132
  *
@@ -130,55 +134,165 @@ type TaggedErrorWithoutName<T extends string> = Omit<TaggedError<T>, "name">;
130
134
  * ```ts
131
135
  * type NetworkErr = ReplaceErrorWithErr<"NetworkError">; // "NetworkErr"
132
136
  * type ValidationErr = ReplaceErrorWithErr<"ValidationError">; // "ValidationErr"
133
- * type AuthErr = ReplaceErrorWithErr<"AuthError">; // "AuthErr"
134
137
  * ```
135
138
  */
136
139
  type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
137
140
  /**
138
- * Return type for createTaggedError - contains both factory functions.
139
- *
140
- * Provides two factory functions:
141
- * - One that creates plain TaggedError objects (named with "Error" suffix)
142
- * - 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.
143
153
  */
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>> };
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> };
145
155
  /**
146
- * Returns two different factory functions for tagged errors.
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.
215
+ */
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
+ };
227
+ /**
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.
147
258
  *
148
259
  * Given an error name like "NetworkError", this returns:
149
260
  * - `NetworkError`: Creates a plain TaggedError object
150
261
  * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
151
262
  *
152
- * The naming pattern automatically replaces the "Error" suffix with "Err" suffix
153
- * for the Result-wrapped version.
263
+ * **Three usage modes:**
154
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
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)
155
272
  * @param name - The name of the error type (must end with "Error")
156
- * @returns An object with two factory functions:
157
- * - [name]: Function that creates plain TaggedError objects
158
- * - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
159
273
  *
160
274
  * @example
161
275
  * ```ts
276
+ * // Mode 1: Flexible - context optional, any shape
162
277
  * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
278
+ * NetworkError({ message: 'Connection failed' });
279
+ * NetworkError({ message: 'Timeout', context: { url: 'https://...' } });
163
280
  *
164
- * // NetworkError: Creates just the error object
165
- * const error = NetworkError({
166
- * message: 'Connection failed',
167
- * context: { url: 'https://api.example.com' },
168
- * cause: undefined
169
- * });
170
- * // Returns: { 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
171
286
  *
172
- * // NetworkErr: Creates error and wraps in Err result
173
- * return NetworkErr({
174
- * message: 'Connection failed',
175
- * context: { url: 'https://api.example.com' },
176
- * cause: undefined
177
- * });
178
- * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
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 });
179
291
  * ```
180
292
  */
181
- declare function createTaggedError<TErrorName extends `${string}Error`>(name: TErrorName): TaggedErrorFactories<TErrorName>;
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>;
182
296
  //#endregion
183
- export { BaseError, TaggedError, createTaggedError, extractErrorMessage };
297
+ export { TaggedError, createTaggedError, extractErrorMessage };
184
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":";;;;;;;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":"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"}
@@ -39,11 +39,22 @@ import { Err } from "../result-B1iWFqM9.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 {
@@ -52,49 +63,13 @@ function extractErrorMessage(error) {
52
63
  }
53
64
  return String(error);
54
65
  }
55
- /**
56
- * Returns two different factory functions for tagged errors.
57
- *
58
- * Given an error name like "NetworkError", this returns:
59
- * - `NetworkError`: Creates a plain TaggedError object
60
- * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
61
- *
62
- * The naming pattern automatically replaces the "Error" suffix with "Err" suffix
63
- * for the Result-wrapped version.
64
- *
65
- * @param name - The name of the error type (must end with "Error")
66
- * @returns An object with two factory functions:
67
- * - [name]: Function that creates plain TaggedError objects
68
- * - [name with "Error" replaced by "Err"]: Function that creates Err-wrapped TaggedError objects
69
- *
70
- * @example
71
- * ```ts
72
- * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');
73
- *
74
- * // NetworkError: Creates just the error object
75
- * const error = NetworkError({
76
- * message: 'Connection failed',
77
- * context: { url: 'https://api.example.com' },
78
- * cause: undefined
79
- * });
80
- * // Returns: { name: 'NetworkError', message: 'Connection failed', ... }
81
- *
82
- * // NetworkErr: Creates error and wraps in Err result
83
- * return NetworkErr({
84
- * message: 'Connection failed',
85
- * context: { url: 'https://api.example.com' },
86
- * cause: undefined
87
- * });
88
- * // Returns: Err({ name: 'NetworkError', message: 'Connection failed', ... })
89
- * ```
90
- */
91
66
  function createTaggedError(name) {
92
- const errorConstructor = (error) => ({
93
- ...error,
94
- name
67
+ const errorConstructor = (input) => ({
68
+ name,
69
+ ...input
95
70
  });
96
71
  const errName = name.replace(/Error$/, "Err");
97
- const errConstructor = (error) => Err(errorConstructor(error));
72
+ const errConstructor = (input) => Err(errorConstructor(input));
98
73
  return {
99
74
  [name]: errorConstructor,
100
75
  [errName]: errConstructor
@@ -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: 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"}
@@ -24,7 +24,7 @@ type DefineQueryInput<TQueryFnData = unknown, TError = DefaultError, TData = TQu
24
24
  * Output of defineQuery function.
25
25
  *
26
26
  * Provides both reactive and imperative interfaces for data fetching:
27
- * - `options()`: Returns config for use with createQuery() in components
27
+ * - `options()`: Returns config for use with useQuery() or createQuery()
28
28
  * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)
29
29
  * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
30
30
  *
@@ -58,7 +58,7 @@ type DefineMutationInput<TData, TError, TVariables = void, TContext = unknown> =
58
58
  * Output of defineMutation function.
59
59
  *
60
60
  * Provides both reactive and imperative interfaces for data mutations:
61
- * - `options()`: Returns config for use with createMutation() in Svelte components
61
+ * - `options()`: Returns config for use with useMutation() or createMutation()
62
62
  * - `execute()`: Directly executes the mutation and returns a Result
63
63
  *
64
64
  * @template TData - The type of data returned by the mutation
@@ -105,7 +105,7 @@ type DefineMutationOutput<TData, TError, TVariables = void, TContext = unknown>
105
105
  * });
106
106
  *
107
107
  * // Use in components
108
- * const query = createQuery(userQuery.options());
108
+ * const query = createQuery(userQuery.options);
109
109
  *
110
110
  * // Or imperatively
111
111
  * const { data, error } = await userQuery.fetch();
@@ -37,7 +37,7 @@ import "../result-DfuKgZo9.js";
37
37
  * });
38
38
  *
39
39
  * // Use in components
40
- * const query = createQuery(userQuery.options());
40
+ * const query = createQuery(userQuery.options);
41
41
  *
42
42
  * // Or imperatively
43
43
  * const { data, error } = await userQuery.fetch();
@@ -52,7 +52,7 @@ function createQueryFactories(queryClient) {
52
52
  *
53
53
  * ## Why use defineQuery?
54
54
  *
55
- * 1. **Dual Interface**: Provides both reactive (`.options()`) and imperative (`.fetch()`) APIs
55
+ * 1. **Dual Interface**: Provides both reactive (`.options`) and imperative (`.fetch()`) APIs
56
56
  * 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically
57
57
  * unwrapped by TanStack Query, giving you proper error states in your components
58
58
  * 3. **Type Safety**: Full TypeScript support with proper inference for data and error types
@@ -69,7 +69,7 @@ function createQueryFactories(queryClient) {
69
69
  * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)
70
70
  *
71
71
  * @returns Query definition object with three methods:
72
- * - `options()`: Returns config for use with createQuery() in Svelte components
72
+ * - `options()`: Returns config for use with useQuery() or createQuery()
73
73
  * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)
74
74
  * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
75
75
  *
@@ -83,7 +83,7 @@ function createQueryFactories(queryClient) {
83
83
  * });
84
84
  *
85
85
  * // Step 2a: Use reactively in a Svelte component
86
- * const query = createQuery(userQuery.options());
86
+ * const query = createQuery(userQuery.options);
87
87
  * // $query.data is User | undefined
88
88
  * // $query.error is ApiError | null
89
89
  *
@@ -163,7 +163,7 @@ function createQueryFactories(queryClient) {
163
163
  * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)
164
164
  *
165
165
  * @returns Mutation definition object with two methods:
166
- * - `options()`: Returns config for use with createMutation() in Svelte components
166
+ * - `options()`: Returns config for use with useMutation() or createMutation()
167
167
  * - `execute()`: Directly executes the mutation and returns a Result
168
168
  *
169
169
  * @example
@@ -186,7 +186,7 @@ function createQueryFactories(queryClient) {
186
186
  * });
187
187
  *
188
188
  * // Step 2a: Use reactively in a component
189
- * const mutation = createMutation(createRecording.options());
189
+ * const mutation = createMutation(createRecording.options);
190
190
  * // Call with: $mutation.mutate(recordingData)
191
191
  *
192
192
  * // Step 2b: Use imperatively in an action
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["queryClient: QueryClient","options: DefineQueryInput<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>","options: DefineMutationInput<TData, TError, TVariables, TContext>","variables: TVariables","options: MutationOptions<TData, TError, TVariables, TContext>"],"sources":["../../src/query/utils.ts"],"sourcesContent":["import type {\n\tDefaultError,\n\tMutationFunction,\n\tMutationKey,\n\tMutationOptions,\n\tQueryClient,\n\tQueryFunction,\n\tQueryKey,\n\tQueryObserverOptions,\n} from \"@tanstack/query-core\";\nimport { Err, Ok, type Result, resolve } from \"../result/index.js\";\n\n/**\n * Input options for defining a query.\n *\n * Extends TanStack Query's QueryObserverOptions but replaces queryFn with resultQueryFn.\n * This type represents the configuration for creating a query definition with both\n * reactive and imperative interfaces for data fetching.\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryInput<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = Omit<\n\tQueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,\n\t\"queryFn\"\n> & {\n\tqueryKey: TQueryKey;\n\tresultQueryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;\n};\n\n/**\n * Output of defineQuery function.\n *\n * Provides both reactive and imperative interfaces for data fetching:\n * - `options()`: Returns config for use with createQuery() in components\n * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)\n * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryOutput<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = {\n\toptions: () => QueryObserverOptions<\n\t\tTQueryFnData,\n\t\tTError,\n\t\tTData,\n\t\tTQueryData,\n\t\tTQueryKey\n\t>;\n\tfetch: () => Promise<Result<TQueryData, TError>>;\n\tensure: () => Promise<Result<TQueryData, TError>>;\n};\n\n/**\n * Input options for defining a mutation.\n *\n * Extends TanStack Query's MutationOptions but replaces mutationFn with resultMutationFn.\n * This type represents the configuration for creating a mutation definition with both\n * reactive and imperative interfaces for data mutations.\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationInput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = Omit<MutationOptions<TData, TError, TVariables, TContext>, \"mutationFn\"> & {\n\tmutationKey: MutationKey;\n\tresultMutationFn: MutationFunction<Result<TData, TError>, TVariables>;\n};\n\n/**\n * Output of defineMutation function.\n *\n * Provides both reactive and imperative interfaces for data mutations:\n * - `options()`: Returns config for use with createMutation() in Svelte components\n * - `execute()`: Directly executes the mutation and returns a Result\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationOutput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = {\n\toptions: () => MutationOptions<TData, TError, TVariables, TContext>;\n\texecute: (variables: TVariables) => Promise<Result<TData, TError>>;\n};\n\n/**\n * Creates factory functions for defining queries and mutations bound to a specific QueryClient.\n *\n * This factory pattern allows you to create isolated query/mutation definitions that are\n * bound to a specific QueryClient instance, enabling:\n * - Multiple query clients in the same application\n * - Testing with isolated query clients\n * - Framework-agnostic query definitions\n * - Proper separation of concerns between query logic and client instances\n *\n * The returned functions handle Result types automatically, unwrapping them for TanStack Query\n * while maintaining type safety throughout your application.\n *\n * @param queryClient - The QueryClient instance to bind the factories to\n * @returns An object containing defineQuery and defineMutation functions bound to the provided client\n *\n * @example\n * ```typescript\n * // Create your query client\n * const queryClient = new QueryClient({\n * defaultOptions: {\n * queries: { staleTime: 5 * 60 * 1000 }\n * }\n * });\n *\n * // Create the factory functions\n * const { defineQuery, defineMutation } = createQueryFactories(queryClient);\n *\n * // Now use defineQuery and defineMutation as before\n * const userQuery = defineQuery({\n * queryKey: ['user', userId],\n * resultQueryFn: () => services.getUser(userId)\n * });\n *\n * // Use in components\n * const query = createQuery(userQuery.options());\n *\n * // Or imperatively\n * const { data, error } = await userQuery.fetch();\n * ```\n */\nexport function createQueryFactories(queryClient: QueryClient) {\n\t/**\n\t * Creates a query definition that bridges the gap between pure service functions and reactive UI components.\n\t *\n\t * This factory function is the cornerstone of our data fetching architecture. It wraps service calls\n\t * with TanStack Query superpowers while maintaining type safety through Result types.\n\t *\n\t * ## Why use defineQuery?\n\t *\n\t * 1. **Dual Interface**: Provides both reactive (`.options()`) and imperative (`.fetch()`) APIs\n\t * 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically\n\t * unwrapped by TanStack Query, giving you proper error states in your components\n\t * 3. **Type Safety**: Full TypeScript support with proper inference for data and error types\n\t * 4. **Consistency**: Every query in the app follows the same pattern, making it easy to understand\n\t *\n\t * @template TQueryFnData - The type of data returned by the query function\n\t * @template TError - The type of error that can be thrown\n\t * @template TData - The type of data returned by the query (after select transform)\n\t * @template TQueryKey - The type of the query key\n\t *\n\t * @param options - Query configuration object\n\t * @param options.queryKey - Unique key for this query (used for caching and refetching)\n\t * @param options.resultQueryFn - Function that fetches data and returns a Result type\n\t * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)\n\t *\n\t * @returns Query definition object with three methods:\n\t * - `options()`: Returns config for use with createQuery() in Svelte components\n\t * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)\n\t * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your query in the query layer\n\t * const userQuery = defineQuery({\n\t * queryKey: ['users', userId],\n\t * resultQueryFn: () => services.getUser(userId), // Returns Result<User, ApiError>\n\t * staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a Svelte component\n\t * const query = createQuery(userQuery.options());\n\t * // $query.data is User | undefined\n\t * // $query.error is ApiError | null\n\t *\n\t * // Step 2b: Use imperatively in preloaders (recommended)\n\t * export const load = async () => {\n\t * const { data, error } = await userQuery.ensure();\n\t * if (error) throw error;\n\t * return { user: data };\n\t * };\n\t *\n\t * // Step 2c: Use imperatively for explicit refresh\n\t * async function refreshUser() {\n\t * const { data, error } = await userQuery.fetch();\n\t * if (error) {\n\t * console.error('Failed to fetch user:', error);\n\t * }\n\t * }\n\t * ```\n\t */\n\tconst defineQuery = <\n\t\tTQueryFnData = unknown,\n\t\tTError = DefaultError,\n\t\tTData = TQueryFnData,\n\t\tTQueryData = TQueryFnData,\n\t\tTQueryKey extends QueryKey = QueryKey,\n\t>(\n\t\toptions: DefineQueryInput<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>,\n\t): DefineQueryOutput<TQueryFnData, TError, TData, TQueryData, TQueryKey> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tqueryFn: async (context) => {\n\t\t\t\tlet result = options.resultQueryFn(context);\n\t\t\t\tif (result instanceof Promise) result = await result;\n\t\t\t\treturn resolve(result);\n\t\t\t},\n\t\t} satisfies QueryObserverOptions<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the query options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `createQuery()` in Svelte components for automatic subscriptions.\n\t\t\t * @returns The query options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\n\t\t\t/**\n\t\t\t * Fetches data for this query using queryClient.fetchQuery().\n\t\t\t *\n\t\t\t * This method ALWAYS evaluates freshness and will refetch if data is stale.\n\t\t\t * It wraps TanStack Query's fetchQuery method, which returns cached data if fresh\n\t\t\t * or makes a network request if the data is stale or missing.\n\t\t\t *\n\t\t\t * **When to use fetch():**\n\t\t\t * - When you explicitly want to check data freshness\n\t\t\t * - For user-triggered refresh actions\n\t\t\t * - When you need the most up-to-date data\n\t\t\t *\n\t\t\t * **For preloaders, use ensure() instead** - it's more efficient for initial data loading.\n\t\t\t *\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // Good for user-triggered refresh\n\t\t\t * const { data, error } = await userQuery.fetch();\n\t\t\t * if (error) {\n\t\t\t * console.error('Failed to load user:', error);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync fetch(): Promise<Result<TQueryData, TError>> {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(\n\t\t\t\t\t\tawait queryClient.fetchQuery<\n\t\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\t\tTError,\n\t\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t\t>({\n\t\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Ensures data is available for this query using queryClient.ensureQueryData().\n\t\t\t *\n\t\t\t * This method PRIORITIZES cached data and only calls fetchQuery internally if no cached\n\t\t\t * data exists. It wraps TanStack Query's ensureQueryData method, which is perfect for\n\t\t\t * guaranteeing data availability with minimal network requests.\n\t\t\t *\n\t\t\t * **This is the RECOMMENDED method for preloaders** because:\n\t\t\t * - It returns cached data immediately if available\n\t\t\t * - It updates the query client cache properly\n\t\t\t * - It minimizes network requests during navigation\n\t\t\t * - It ensures components have data ready when they mount\n\t\t\t *\n\t\t\t * **When to use ensure():**\n\t\t\t * - Route preloaders and data loading functions\n\t\t\t * - Initial component data requirements\n\t\t\t * - When cached data is acceptable for immediate display\n\t\t\t *\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // Perfect for preloaders\n\t\t\t * export const load = async () => {\n\t\t\t * const { data, error } = await userQuery.ensure();\n\t\t\t * if (error) {\n\t\t\t * throw error;\n\t\t\t * }\n\t\t\t * return { user: data };\n\t\t\t * };\n\t\t\t */\n\t\t\tasync ensure(): Promise<Result<TQueryData, TError>> {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(\n\t\t\t\t\t\tawait queryClient.ensureQueryData<\n\t\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\t\tTError,\n\t\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t\t>({\n\t\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\t/**\n\t * Creates a mutation definition for operations that modify data (create, update, delete).\n\t *\n\t * This factory function is the mutation counterpart to defineQuery. It provides a clean way to\n\t * wrap service functions that perform side effects, while maintaining the same dual interface\n\t * pattern for maximum flexibility.\n\t *\n\t * ## Why use defineMutation?\n\t *\n\t * 1. **Dual Interface**: Just like queries, mutations can be used reactively or imperatively\n\t * 2. **Direct Execution**: The `.execute()` method lets you run mutations without creating hooks,\n\t * perfect for event handlers and non-component code\n\t * 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring\n\t * errors are handled consistently throughout the app\n\t * 4. **Cache Management**: Mutations often update the cache after success (see examples)\n\t *\n\t * @template TData - The type of data returned by the mutation\n\t * @template TError - The type of error that can be thrown\n\t * @template TVariables - The type of variables passed to the mutation\n\t * @template TContext - The type of context data for optimistic updates\n\t *\n\t * @param options - Mutation configuration object\n\t * @param options.mutationKey - Unique key for this mutation (used for tracking in-flight state)\n\t * @param options.resultMutationFn - Function that performs the mutation and returns a Result type\n\t * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)\n\t *\n\t * @returns Mutation definition object with two methods:\n\t * - `options()`: Returns config for use with createMutation() in Svelte components\n\t * - `execute()`: Directly executes the mutation and returns a Result\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your mutation with cache updates\n\t * const createRecording = defineMutation({\n\t * mutationKey: ['recordings', 'create'],\n\t * resultMutationFn: async (recording: Recording) => {\n\t * // Call the service\n\t * const result = await services.db.createRecording(recording);\n\t * if (result.error) return Err(result.error);\n\t *\n\t * // Update cache on success\n\t * queryClient.setQueryData(['recordings'], (old) =>\n\t * [...(old || []), recording]\n\t * );\n\t *\n\t * return Ok(result.data);\n\t * }\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a component\n\t * const mutation = createMutation(createRecording.options());\n\t * // Call with: $mutation.mutate(recordingData)\n\t *\n\t * // Step 2b: Use imperatively in an action\n\t * async function saveRecording(data: Recording) {\n\t * const { error } = await createRecording.execute(data);\n\t * if (error) {\n\t * notify.error.execute({ title: 'Failed to save', description: error.message });\n\t * } else {\n\t * notify.success.execute({ title: 'Recording saved!' });\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @tip The imperative `.execute()` method is especially useful for:\n\t * - Event handlers that need to await the result\n\t * - Sequential operations that depend on each other\n\t * - Non-component code that needs to trigger mutations\n\t */\n\tconst defineMutation = <TData, TError, TVariables = void, TContext = unknown>(\n\t\toptions: DefineMutationInput<TData, TError, TVariables, TContext>,\n\t): DefineMutationOutput<TData, TError, TVariables, TContext> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tmutationFn: async (variables: TVariables) => {\n\t\t\t\treturn resolve(await options.resultMutationFn(variables));\n\t\t\t},\n\t\t} satisfies MutationOptions<TData, TError, TVariables, TContext>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the mutation options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `createMutation()` in Svelte components for reactive mutation state.\n\t\t\t * @returns The mutation options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\t\t\t/**\n\t\t\t * Bypasses the reactive mutation hooks and executes the mutation imperatively.\n\t\t\t *\n\t\t\t * This is the recommended way to trigger mutations from:\n\t\t\t * - Button click handlers\n\t\t\t * - Form submissions\n\t\t\t * - Keyboard shortcuts\n\t\t\t * - Any non-component code\n\t\t\t *\n\t\t\t * The method automatically wraps the result in a Result type, so you always\n\t\t\t * get back `{ data, error }` for consistent error handling.\n\t\t\t *\n\t\t\t * @param variables - The variables to pass to the mutation function\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // In an event handler\n\t\t\t * async function handleSubmit(formData: FormData) {\n\t\t\t * const { data, error } = await createUser.execute(formData);\n\t\t\t * if (error) {\n\t\t\t * notify.error.execute({ title: 'Failed to create user', description: error.message });\n\t\t\t * return;\n\t\t\t * }\n\t\t\t * goto(`/users/${data.id}`);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync execute(variables: TVariables) {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(await executeMutation(queryClient, newOptions, variables));\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\treturn {\n\t\tdefineQuery,\n\t\tdefineMutation,\n\t};\n}\n\n/**\n * Internal helper that executes a mutation directly using the query client's mutation cache.\n *\n * This is what powers the `.execute()` method on mutations. It bypasses the reactive\n * mutation hooks and runs the mutation imperatively, which is perfect for event handlers\n * and other imperative code.\n *\n * @internal\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data\n * @param queryClient - The query client instance to use\n * @param options - The mutation options including mutationFn and mutationKey\n * @param variables - The variables to pass to the mutation function\n * @returns Promise that resolves with the mutation result\n */\nfunction executeMutation<TData, TError, TVariables, TContext>(\n\tqueryClient: QueryClient,\n\toptions: MutationOptions<TData, TError, TVariables, TContext>,\n\tvariables: TVariables,\n) {\n\tconst mutation = queryClient.getMutationCache().build(queryClient, options);\n\treturn mutation.execute(variables);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0JA,SAAgB,qBAAqBA,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4D9D,MAAM,cAAc,CAOnBC,YAO2E;EAC3E,MAAM,aAAa;GAClB,GAAG;GACH,SAAS,OAAO,YAAY;IAC3B,IAAI,SAAS,QAAQ,cAAc,QAAQ;AAC3C,QAAI,kBAAkB,QAAS,UAAS,MAAM;AAC9C,WAAO,QAAQ,OAAO;GACtB;EACD;AAQD,SAAO;GAMN,SAAS,MAAM;GAyBf,MAAM,QAA6C;AAClD,QAAI;AACH,YAAO,GACN,MAAM,YAAY,WAKhB;MACD,UAAU,WAAW;MACrB,SAAS,WAAW;KACpB,EAAC,CACF;IACD,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;GAgCD,MAAM,SAA8C;AACnD,QAAI;AACH,YAAO,GACN,MAAM,YAAY,gBAKhB;MACD,UAAU,WAAW;MACrB,SAAS,WAAW;KACpB,EAAC,CACF;IACD,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuED,MAAM,iBAAiB,CACtBC,YAC+D;EAC/D,MAAM,aAAa;GAClB,GAAG;GACH,YAAY,OAAOC,cAA0B;AAC5C,WAAO,QAAQ,MAAM,QAAQ,iBAAiB,UAAU,CAAC;GACzD;EACD;AAED,SAAO;GAMN,SAAS,MAAM;GA2Bf,MAAM,QAAQA,WAAuB;AACpC,QAAI;AACH,YAAO,GAAG,MAAM,gBAAgB,aAAa,YAAY,UAAU,CAAC;IACpE,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;AAED,QAAO;EACN;EACA;CACA;AACD;;;;;;;;;;;;;;;;;;AAmBD,SAAS,gBACRH,aACAI,SACAD,WACC;CACD,MAAM,WAAW,YAAY,kBAAkB,CAAC,MAAM,aAAa,QAAQ;AAC3E,QAAO,SAAS,QAAQ,UAAU;AAClC"}
1
+ {"version":3,"file":"index.js","names":["queryClient: QueryClient","options: DefineQueryInput<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>","options: DefineMutationInput<TData, TError, TVariables, TContext>","variables: TVariables","options: MutationOptions<TData, TError, TVariables, TContext>"],"sources":["../../src/query/utils.ts"],"sourcesContent":["import type {\n\tDefaultError,\n\tMutationFunction,\n\tMutationKey,\n\tMutationOptions,\n\tQueryClient,\n\tQueryFunction,\n\tQueryKey,\n\tQueryObserverOptions,\n} from \"@tanstack/query-core\";\nimport { Err, Ok, type Result, resolve } from \"../result/index.js\";\n\n/**\n * Input options for defining a query.\n *\n * Extends TanStack Query's QueryObserverOptions but replaces queryFn with resultQueryFn.\n * This type represents the configuration for creating a query definition with both\n * reactive and imperative interfaces for data fetching.\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryInput<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = Omit<\n\tQueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,\n\t\"queryFn\"\n> & {\n\tqueryKey: TQueryKey;\n\tresultQueryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;\n};\n\n/**\n * Output of defineQuery function.\n *\n * Provides both reactive and imperative interfaces for data fetching:\n * - `options()`: Returns config for use with useQuery() or createQuery()\n * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)\n * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)\n *\n * @template TQueryFnData - The type of data returned by the query function\n * @template TError - The type of error that can be thrown\n * @template TData - The type of data returned by the query (after select transform)\n * @template TQueryKey - The type of the query key\n */\nexport type DefineQueryOutput<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = {\n\toptions: () => QueryObserverOptions<\n\t\tTQueryFnData,\n\t\tTError,\n\t\tTData,\n\t\tTQueryData,\n\t\tTQueryKey\n\t>;\n\tfetch: () => Promise<Result<TQueryData, TError>>;\n\tensure: () => Promise<Result<TQueryData, TError>>;\n};\n\n/**\n * Input options for defining a mutation.\n *\n * Extends TanStack Query's MutationOptions but replaces mutationFn with resultMutationFn.\n * This type represents the configuration for creating a mutation definition with both\n * reactive and imperative interfaces for data mutations.\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationInput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = Omit<MutationOptions<TData, TError, TVariables, TContext>, \"mutationFn\"> & {\n\tmutationKey: MutationKey;\n\tresultMutationFn: MutationFunction<Result<TData, TError>, TVariables>;\n};\n\n/**\n * Output of defineMutation function.\n *\n * Provides both reactive and imperative interfaces for data mutations:\n * - `options()`: Returns config for use with useMutation() or createMutation()\n * - `execute()`: Directly executes the mutation and returns a Result\n *\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data for optimistic updates\n */\nexport type DefineMutationOutput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = {\n\toptions: () => MutationOptions<TData, TError, TVariables, TContext>;\n\texecute: (variables: TVariables) => Promise<Result<TData, TError>>;\n};\n\n/**\n * Creates factory functions for defining queries and mutations bound to a specific QueryClient.\n *\n * This factory pattern allows you to create isolated query/mutation definitions that are\n * bound to a specific QueryClient instance, enabling:\n * - Multiple query clients in the same application\n * - Testing with isolated query clients\n * - Framework-agnostic query definitions\n * - Proper separation of concerns between query logic and client instances\n *\n * The returned functions handle Result types automatically, unwrapping them for TanStack Query\n * while maintaining type safety throughout your application.\n *\n * @param queryClient - The QueryClient instance to bind the factories to\n * @returns An object containing defineQuery and defineMutation functions bound to the provided client\n *\n * @example\n * ```typescript\n * // Create your query client\n * const queryClient = new QueryClient({\n * defaultOptions: {\n * queries: { staleTime: 5 * 60 * 1000 }\n * }\n * });\n *\n * // Create the factory functions\n * const { defineQuery, defineMutation } = createQueryFactories(queryClient);\n *\n * // Now use defineQuery and defineMutation as before\n * const userQuery = defineQuery({\n * queryKey: ['user', userId],\n * resultQueryFn: () => services.getUser(userId)\n * });\n *\n * // Use in components\n * const query = createQuery(userQuery.options);\n *\n * // Or imperatively\n * const { data, error } = await userQuery.fetch();\n * ```\n */\nexport function createQueryFactories(queryClient: QueryClient) {\n\t/**\n\t * Creates a query definition that bridges the gap between pure service functions and reactive UI components.\n\t *\n\t * This factory function is the cornerstone of our data fetching architecture. It wraps service calls\n\t * with TanStack Query superpowers while maintaining type safety through Result types.\n\t *\n\t * ## Why use defineQuery?\n\t *\n\t * 1. **Dual Interface**: Provides both reactive (`.options`) and imperative (`.fetch()`) APIs\n\t * 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically\n\t * unwrapped by TanStack Query, giving you proper error states in your components\n\t * 3. **Type Safety**: Full TypeScript support with proper inference for data and error types\n\t * 4. **Consistency**: Every query in the app follows the same pattern, making it easy to understand\n\t *\n\t * @template TQueryFnData - The type of data returned by the query function\n\t * @template TError - The type of error that can be thrown\n\t * @template TData - The type of data returned by the query (after select transform)\n\t * @template TQueryKey - The type of the query key\n\t *\n\t * @param options - Query configuration object\n\t * @param options.queryKey - Unique key for this query (used for caching and refetching)\n\t * @param options.resultQueryFn - Function that fetches data and returns a Result type\n\t * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)\n\t *\n\t * @returns Query definition object with three methods:\n\t * - `options()`: Returns config for use with useQuery() or createQuery()\n\t * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)\n\t * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your query in the query layer\n\t * const userQuery = defineQuery({\n\t * queryKey: ['users', userId],\n\t * resultQueryFn: () => services.getUser(userId), // Returns Result<User, ApiError>\n\t * staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a Svelte component\n\t * const query = createQuery(userQuery.options);\n\t * // $query.data is User | undefined\n\t * // $query.error is ApiError | null\n\t *\n\t * // Step 2b: Use imperatively in preloaders (recommended)\n\t * export const load = async () => {\n\t * const { data, error } = await userQuery.ensure();\n\t * if (error) throw error;\n\t * return { user: data };\n\t * };\n\t *\n\t * // Step 2c: Use imperatively for explicit refresh\n\t * async function refreshUser() {\n\t * const { data, error } = await userQuery.fetch();\n\t * if (error) {\n\t * console.error('Failed to fetch user:', error);\n\t * }\n\t * }\n\t * ```\n\t */\n\tconst defineQuery = <\n\t\tTQueryFnData = unknown,\n\t\tTError = DefaultError,\n\t\tTData = TQueryFnData,\n\t\tTQueryData = TQueryFnData,\n\t\tTQueryKey extends QueryKey = QueryKey,\n\t>(\n\t\toptions: DefineQueryInput<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>,\n\t): DefineQueryOutput<TQueryFnData, TError, TData, TQueryData, TQueryKey> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tqueryFn: async (context) => {\n\t\t\t\tlet result = options.resultQueryFn(context);\n\t\t\t\tif (result instanceof Promise) result = await result;\n\t\t\t\treturn resolve(result);\n\t\t\t},\n\t\t} satisfies QueryObserverOptions<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the query options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `useQuery()` or `createQuery()` for automatic subscriptions.\n\t\t\t * @returns The query options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\n\t\t\t/**\n\t\t\t * Fetches data for this query using queryClient.fetchQuery().\n\t\t\t *\n\t\t\t * This method ALWAYS evaluates freshness and will refetch if data is stale.\n\t\t\t * It wraps TanStack Query's fetchQuery method, which returns cached data if fresh\n\t\t\t * or makes a network request if the data is stale or missing.\n\t\t\t *\n\t\t\t * **When to use fetch():**\n\t\t\t * - When you explicitly want to check data freshness\n\t\t\t * - For user-triggered refresh actions\n\t\t\t * - When you need the most up-to-date data\n\t\t\t *\n\t\t\t * **For preloaders, use ensure() instead** - it's more efficient for initial data loading.\n\t\t\t *\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // Good for user-triggered refresh\n\t\t\t * const { data, error } = await userQuery.fetch();\n\t\t\t * if (error) {\n\t\t\t * console.error('Failed to load user:', error);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync fetch(): Promise<Result<TQueryData, TError>> {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(\n\t\t\t\t\t\tawait queryClient.fetchQuery<\n\t\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\t\tTError,\n\t\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t\t>({\n\t\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Ensures data is available for this query using queryClient.ensureQueryData().\n\t\t\t *\n\t\t\t * This method PRIORITIZES cached data and only calls fetchQuery internally if no cached\n\t\t\t * data exists. It wraps TanStack Query's ensureQueryData method, which is perfect for\n\t\t\t * guaranteeing data availability with minimal network requests.\n\t\t\t *\n\t\t\t * **This is the RECOMMENDED method for preloaders** because:\n\t\t\t * - It returns cached data immediately if available\n\t\t\t * - It updates the query client cache properly\n\t\t\t * - It minimizes network requests during navigation\n\t\t\t * - It ensures components have data ready when they mount\n\t\t\t *\n\t\t\t * **When to use ensure():**\n\t\t\t * - Route preloaders and data loading functions\n\t\t\t * - Initial component data requirements\n\t\t\t * - When cached data is acceptable for immediate display\n\t\t\t *\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // Perfect for preloaders\n\t\t\t * export const load = async () => {\n\t\t\t * const { data, error } = await userQuery.ensure();\n\t\t\t * if (error) {\n\t\t\t * throw error;\n\t\t\t * }\n\t\t\t * return { user: data };\n\t\t\t * };\n\t\t\t */\n\t\t\tasync ensure(): Promise<Result<TQueryData, TError>> {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(\n\t\t\t\t\t\tawait queryClient.ensureQueryData<\n\t\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\t\tTError,\n\t\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t\t>({\n\t\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\t/**\n\t * Creates a mutation definition for operations that modify data (create, update, delete).\n\t *\n\t * This factory function is the mutation counterpart to defineQuery. It provides a clean way to\n\t * wrap service functions that perform side effects, while maintaining the same dual interface\n\t * pattern for maximum flexibility.\n\t *\n\t * ## Why use defineMutation?\n\t *\n\t * 1. **Dual Interface**: Just like queries, mutations can be used reactively or imperatively\n\t * 2. **Direct Execution**: The `.execute()` method lets you run mutations without creating hooks,\n\t * perfect for event handlers and non-component code\n\t * 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring\n\t * errors are handled consistently throughout the app\n\t * 4. **Cache Management**: Mutations often update the cache after success (see examples)\n\t *\n\t * @template TData - The type of data returned by the mutation\n\t * @template TError - The type of error that can be thrown\n\t * @template TVariables - The type of variables passed to the mutation\n\t * @template TContext - The type of context data for optimistic updates\n\t *\n\t * @param options - Mutation configuration object\n\t * @param options.mutationKey - Unique key for this mutation (used for tracking in-flight state)\n\t * @param options.resultMutationFn - Function that performs the mutation and returns a Result type\n\t * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)\n\t *\n\t * @returns Mutation definition object with two methods:\n\t * - `options()`: Returns config for use with useMutation() or createMutation()\n\t * - `execute()`: Directly executes the mutation and returns a Result\n\t *\n\t * @example\n\t * ```typescript\n\t * // Step 1: Define your mutation with cache updates\n\t * const createRecording = defineMutation({\n\t * mutationKey: ['recordings', 'create'],\n\t * resultMutationFn: async (recording: Recording) => {\n\t * // Call the service\n\t * const result = await services.db.createRecording(recording);\n\t * if (result.error) return Err(result.error);\n\t *\n\t * // Update cache on success\n\t * queryClient.setQueryData(['recordings'], (old) =>\n\t * [...(old || []), recording]\n\t * );\n\t *\n\t * return Ok(result.data);\n\t * }\n\t * });\n\t *\n\t * // Step 2a: Use reactively in a component\n\t * const mutation = createMutation(createRecording.options);\n\t * // Call with: $mutation.mutate(recordingData)\n\t *\n\t * // Step 2b: Use imperatively in an action\n\t * async function saveRecording(data: Recording) {\n\t * const { error } = await createRecording.execute(data);\n\t * if (error) {\n\t * notify.error.execute({ title: 'Failed to save', description: error.message });\n\t * } else {\n\t * notify.success.execute({ title: 'Recording saved!' });\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @tip The imperative `.execute()` method is especially useful for:\n\t * - Event handlers that need to await the result\n\t * - Sequential operations that depend on each other\n\t * - Non-component code that needs to trigger mutations\n\t */\n\tconst defineMutation = <TData, TError, TVariables = void, TContext = unknown>(\n\t\toptions: DefineMutationInput<TData, TError, TVariables, TContext>,\n\t): DefineMutationOutput<TData, TError, TVariables, TContext> => {\n\t\tconst newOptions = {\n\t\t\t...options,\n\t\t\tmutationFn: async (variables: TVariables) => {\n\t\t\t\treturn resolve(await options.resultMutationFn(variables));\n\t\t\t},\n\t\t} satisfies MutationOptions<TData, TError, TVariables, TContext>;\n\n\t\treturn {\n\t\t\t/**\n\t\t\t * Returns the mutation options for reactive usage with TanStack Query hooks.\n\t\t\t * Use this with `useMutation()` or `createMutation()` for reactive mutation state.\n\t\t\t * @returns The mutation options object configured for TanStack Query\n\t\t\t */\n\t\t\toptions: () => newOptions,\n\t\t\t/**\n\t\t\t * Bypasses the reactive mutation hooks and executes the mutation imperatively.\n\t\t\t *\n\t\t\t * This is the recommended way to trigger mutations from:\n\t\t\t * - Button click handlers\n\t\t\t * - Form submissions\n\t\t\t * - Keyboard shortcuts\n\t\t\t * - Any non-component code\n\t\t\t *\n\t\t\t * The method automatically wraps the result in a Result type, so you always\n\t\t\t * get back `{ data, error }` for consistent error handling.\n\t\t\t *\n\t\t\t * @param variables - The variables to pass to the mutation function\n\t\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t\t *\n\t\t\t * @example\n\t\t\t * // In an event handler\n\t\t\t * async function handleSubmit(formData: FormData) {\n\t\t\t * const { data, error } = await createUser.execute(formData);\n\t\t\t * if (error) {\n\t\t\t * notify.error.execute({ title: 'Failed to create user', description: error.message });\n\t\t\t * return;\n\t\t\t * }\n\t\t\t * goto(`/users/${data.id}`);\n\t\t\t * }\n\t\t\t */\n\t\t\tasync execute(variables: TVariables) {\n\t\t\t\ttry {\n\t\t\t\t\treturn Ok(await executeMutation(queryClient, newOptions, variables));\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn Err(error as TError);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t};\n\n\treturn {\n\t\tdefineQuery,\n\t\tdefineMutation,\n\t};\n}\n\n/**\n * Internal helper that executes a mutation directly using the query client's mutation cache.\n *\n * This is what powers the `.execute()` method on mutations. It bypasses the reactive\n * mutation hooks and runs the mutation imperatively, which is perfect for event handlers\n * and other imperative code.\n *\n * @internal\n * @template TData - The type of data returned by the mutation\n * @template TError - The type of error that can be thrown\n * @template TVariables - The type of variables passed to the mutation\n * @template TContext - The type of context data\n * @param queryClient - The query client instance to use\n * @param options - The mutation options including mutationFn and mutationKey\n * @param variables - The variables to pass to the mutation function\n * @returns Promise that resolves with the mutation result\n */\nfunction executeMutation<TData, TError, TVariables, TContext>(\n\tqueryClient: QueryClient,\n\toptions: MutationOptions<TData, TError, TVariables, TContext>,\n\tvariables: TVariables,\n) {\n\tconst mutation = queryClient.getMutationCache().build(queryClient, options);\n\treturn mutation.execute(variables);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0JA,SAAgB,qBAAqBA,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4D9D,MAAM,cAAc,CAOnBC,YAO2E;EAC3E,MAAM,aAAa;GAClB,GAAG;GACH,SAAS,OAAO,YAAY;IAC3B,IAAI,SAAS,QAAQ,cAAc,QAAQ;AAC3C,QAAI,kBAAkB,QAAS,UAAS,MAAM;AAC9C,WAAO,QAAQ,OAAO;GACtB;EACD;AAQD,SAAO;GAMN,SAAS,MAAM;GAyBf,MAAM,QAA6C;AAClD,QAAI;AACH,YAAO,GACN,MAAM,YAAY,WAKhB;MACD,UAAU,WAAW;MACrB,SAAS,WAAW;KACpB,EAAC,CACF;IACD,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;GAgCD,MAAM,SAA8C;AACnD,QAAI;AACH,YAAO,GACN,MAAM,YAAY,gBAKhB;MACD,UAAU,WAAW;MACrB,SAAS,WAAW;KACpB,EAAC,CACF;IACD,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuED,MAAM,iBAAiB,CACtBC,YAC+D;EAC/D,MAAM,aAAa;GAClB,GAAG;GACH,YAAY,OAAOC,cAA0B;AAC5C,WAAO,QAAQ,MAAM,QAAQ,iBAAiB,UAAU,CAAC;GACzD;EACD;AAED,SAAO;GAMN,SAAS,MAAM;GA2Bf,MAAM,QAAQA,WAAuB;AACpC,QAAI;AACH,YAAO,GAAG,MAAM,gBAAgB,aAAa,YAAY,UAAU,CAAC;IACpE,SAAQ,OAAO;AACf,YAAO,IAAI,MAAgB;IAC3B;GACD;EACD;CACD;AAED,QAAO;EACN;EACA;CACA;AACD;;;;;;;;;;;;;;;;;;AAmBD,SAAS,gBACRH,aACAI,SACAD,WACC;CACD,MAAM,WAAW,YAAY,kBAAkB,CAAC,MAAM,aAAa,QAAQ;AAC3E,QAAO,SAAS,QAAQ,UAAU;AAClC"}
package/package.json CHANGED
@@ -1,56 +1,63 @@
1
1
  {
2
- "name": "wellcrafted",
3
- "version": "0.22.0",
4
- "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
- "type": "module",
6
- "files": [
7
- "dist",
8
- "README.md",
9
- "LICENSE"
10
- ],
11
- "exports": {
12
- "./result": {
13
- "types": "./dist/result/index.d.ts",
14
- "import": "./dist/result/index.js"
15
- },
16
- "./error": {
17
- "types": "./dist/error/index.d.ts",
18
- "import": "./dist/error/index.js"
19
- },
20
- "./brand": {
21
- "types": "./dist/brand.d.ts",
22
- "import": "./dist/brand.js"
23
- },
24
- "./query": {
25
- "types": "./dist/query/index.d.ts",
26
- "import": "./dist/query/index.js"
27
- }
28
- },
29
- "keywords": [
30
- "typescript",
31
- "delightful",
32
- "elegant",
33
- "type-safe",
34
- "well-crafted",
35
- "polished",
36
- "utilities",
37
- "result",
38
- "error-handling",
39
- "brand-types"
40
- ],
41
- "author": "",
42
- "license": "MIT",
43
- "devDependencies": {
44
- "@biomejs/biome": "^1.9.4",
45
- "@changesets/cli": "^2.27.10",
46
- "@tanstack/query-core": "^5.82.0",
47
- "tsdown": "^0.12.5",
48
- "typescript": "^5.8.3"
49
- },
50
- "scripts": {
51
- "build": "tsdown",
52
- "format": "biome format --write .",
53
- "lint": "biome lint --write .",
54
- "release": "pnpm run build && changeset version && changeset publish"
55
- }
56
- }
2
+ "name": "wellcrafted",
3
+ "version": "0.24.0",
4
+ "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "README.md",
9
+ "LICENSE"
10
+ ],
11
+ "exports": {
12
+ "./result": {
13
+ "types": "./dist/result/index.d.ts",
14
+ "import": "./dist/result/index.js"
15
+ },
16
+ "./error": {
17
+ "types": "./dist/error/index.d.ts",
18
+ "import": "./dist/error/index.js"
19
+ },
20
+ "./brand": {
21
+ "types": "./dist/brand.d.ts",
22
+ "import": "./dist/brand.js"
23
+ },
24
+ "./query": {
25
+ "types": "./dist/query/index.d.ts",
26
+ "import": "./dist/query/index.js"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "build": "tsdown",
31
+ "format": "biome format --write .",
32
+ "lint": "biome lint --write .",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "release": "pnpm run build && changeset version && changeset publish"
36
+ },
37
+ "keywords": [
38
+ "typescript",
39
+ "delightful",
40
+ "elegant",
41
+ "type-safe",
42
+ "well-crafted",
43
+ "polished",
44
+ "utilities",
45
+ "result",
46
+ "error-handling",
47
+ "brand-types"
48
+ ],
49
+ "author": "",
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/wellcrafted-dev/wellcrafted"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^2.3.3",
57
+ "@changesets/cli": "^2.27.10",
58
+ "@tanstack/query-core": "^5.82.0",
59
+ "tsdown": "^0.12.5",
60
+ "typescript": "^5.8.3",
61
+ "vitest": "^4.0.14"
62
+ }
63
+ }