wellcrafted 0.25.1 → 0.26.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.
@@ -230,6 +230,16 @@ type BothFixedErrConstructor<TName extends string, TContext extends Record<strin
230
230
  /**
231
231
  * Creates two factory functions for building tagged errors with type-safe error chaining.
232
232
  *
233
+ * @deprecated Use `defineError()` instead for a cleaner fluent API:
234
+ * ```ts
235
+ * // Before
236
+ * const { FileError } = createTaggedError<'FileError', { path: string }>('FileError')
237
+ *
238
+ * // After
239
+ * const { FileError } = defineError('FileError')
240
+ * .withContext<{ path: string }>()
241
+ * ```
242
+ *
233
243
  * Given an error name like "NetworkError", this returns:
234
244
  * - `NetworkError`: Creates a plain TaggedError object
235
245
  * - `NetworkErr`: Creates a TaggedError object wrapped in an Err result
@@ -278,6 +288,149 @@ type BothFixedErrConstructor<TName extends string, TContext extends Record<strin
278
288
  declare function createTaggedError<TName extends `${string}Error`>(name: TName): FlexibleFactories<TName>;
279
289
  declare function createTaggedError<TName extends `${string}Error`, TContext extends Record<string, unknown>>(name: TName): ContextFixedFactories<TName, TContext>;
280
290
  declare function createTaggedError<TName extends `${string}Error`, TContext extends Record<string, unknown>, TCause extends AnyTaggedError>(name: TName): BothFixedFactories<TName, TContext, TCause>;
291
+ /**
292
+ * Helper type that determines optionality based on whether T includes undefined.
293
+ * - If T includes undefined → property is optional
294
+ * - If T does not include undefined → property is required
295
+ */
296
+ type OptionalIfUndefined<T, TKey extends string> = undefined extends T ? { [K in TKey]?: Exclude<T, undefined> } : { [K in TKey]: T };
297
+ /**
298
+ * Input type for error constructors with fluent API context/cause handling.
299
+ */
300
+ type ErrorInput<TContext extends Record<string, unknown> | undefined, TCause extends AnyTaggedError | undefined> = {
301
+ message: string;
302
+ } & (TContext extends undefined ? {
303
+ context?: Record<string, unknown>;
304
+ } : OptionalIfUndefined<TContext, "context">) & (TCause extends undefined ? {
305
+ cause?: AnyTaggedError;
306
+ } : OptionalIfUndefined<TCause, "cause">);
307
+ /**
308
+ * The factories object returned by defineError and its builder methods.
309
+ */
310
+ type ErrorFactories<TName extends `${string}Error`, TContext extends Record<string, unknown> | undefined, TCause extends AnyTaggedError | undefined> = { [K in TName]: (input: ErrorInput<TContext, TCause>) => TaggedError<TName, TContext, TCause> } & { [K in ReplaceErrorWithErr<TName>]: (input: ErrorInput<TContext, TCause>) => Err<TaggedError<TName, TContext, TCause>> };
311
+ /**
312
+ * Builder interface for the fluent defineError API.
313
+ * Provides chaining methods and the error factories.
314
+ */
315
+ type ErrorBuilder<TName extends `${string}Error`, TContext extends Record<string, unknown> | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = ErrorFactories<TName, TContext, TCause> & {
316
+ /**
317
+ * Constrains the context type for this error.
318
+ *
319
+ * Optionality is determined by whether the type includes `undefined`:
320
+ * - `withContext<T>()` where T doesn't include undefined → context is **required**
321
+ * - `withContext<T | undefined>()` → context is **optional** but typed when provided
322
+ *
323
+ * @typeParam T - The shape of the context object. Include `| undefined` to make optional.
324
+ *
325
+ * @example Required context
326
+ * ```ts
327
+ * const { FileError } = defineError('FileError')
328
+ * .withContext<{ path: string }>()
329
+ *
330
+ * FileError({ message: 'Not found', context: { path: '/etc/config' } }) // OK
331
+ * FileError({ message: 'Not found' }) // Type error: context required
332
+ * ```
333
+ *
334
+ * @example Optional but typed context
335
+ * ```ts
336
+ * const { LogError } = defineError('LogError')
337
+ * .withContext<{ file: string; line: number } | undefined>()
338
+ *
339
+ * LogError({ message: 'Parse error' }) // OK
340
+ * LogError({ message: 'Parse error', context: { file: 'app.ts', line: 42 } }) // OK
341
+ * ```
342
+ */
343
+ withContext<T extends Record<string, unknown> | undefined>(): ErrorBuilder<TName, T, TCause>;
344
+ /**
345
+ * Constrains the cause type for this error.
346
+ *
347
+ * Optionality is determined by whether the type includes `undefined`:
348
+ * - `withCause<T>()` where T doesn't include undefined → cause is **required**
349
+ * - `withCause<T | undefined>()` → cause is **optional** but typed when provided
350
+ *
351
+ * Since cause is typically optional, include `| undefined` in most cases.
352
+ *
353
+ * @typeParam T - The allowed cause type(s). Include `| undefined` to make optional.
354
+ *
355
+ * @example Optional typed cause (common)
356
+ * ```ts
357
+ * const { ServiceError } = defineError('ServiceError')
358
+ * .withCause<DbError | CacheError | undefined>()
359
+ *
360
+ * ServiceError({ message: 'Failed' }) // OK
361
+ * ServiceError({ message: 'Failed', cause: dbError }) // OK
362
+ * ```
363
+ *
364
+ * @example Required cause (for wrapper errors)
365
+ * ```ts
366
+ * const { UnhandledError } = defineError('UnhandledError')
367
+ * .withCause<AnyTaggedError>()
368
+ *
369
+ * UnhandledError({ message: 'Unexpected', cause: originalError }) // OK
370
+ * UnhandledError({ message: 'Unexpected' }) // Type error: cause required
371
+ * ```
372
+ */
373
+ withCause<T extends AnyTaggedError | undefined>(): ErrorBuilder<TName, TContext, T>;
374
+ };
375
+ /**
376
+ * Defines a new tagged error type with a fluent builder API.
377
+ *
378
+ * Returns an object containing:
379
+ * - `{Name}Error`: Factory function that creates plain TaggedError objects
380
+ * - `{Name}Err`: Factory function that creates Err-wrapped TaggedError objects
381
+ * - `withContext<T>()`: Chain method to constrain context type
382
+ * - `withCause<T>()`: Chain method to constrain cause type
383
+ *
384
+ * **Default behavior (no chaining):**
385
+ * - `context` is optional and accepts any `Record<string, unknown>`
386
+ * - `cause` is optional and accepts any `AnyTaggedError`
387
+ *
388
+ * **Optionality via type unions:**
389
+ * Both `withContext` and `withCause` determine optionality based on whether
390
+ * the type includes `undefined`:
391
+ * - `T` without undefined → property is required
392
+ * - `T | undefined` → property is optional but typed when provided
393
+ *
394
+ * @template TName - The name of the error type (must end with "Error")
395
+ * @param name - The name of the error type
396
+ *
397
+ * @example Simple error (flexible mode)
398
+ * ```ts
399
+ * const { NetworkError, NetworkErr } = defineError('NetworkError')
400
+ *
401
+ * NetworkError({ message: 'Connection failed' })
402
+ * NetworkError({ message: 'Timeout', context: { url: 'https://...' } })
403
+ * ```
404
+ *
405
+ * @example Required context
406
+ * ```ts
407
+ * const { ApiError, ApiErr } = defineError('ApiError')
408
+ * .withContext<{ endpoint: string; status: number }>()
409
+ *
410
+ * ApiError({ message: 'Failed', context: { endpoint: '/users', status: 500 } })
411
+ * // ApiError({ message: 'Failed' }) // Type error: context required
412
+ * ```
413
+ *
414
+ * @example Optional typed cause
415
+ * ```ts
416
+ * const { ServiceError } = defineError('ServiceError')
417
+ * .withCause<DbError | CacheError | undefined>()
418
+ *
419
+ * ServiceError({ message: 'Failed' }) // OK
420
+ * ServiceError({ message: 'Failed', cause: dbError }) // OK, typed
421
+ * ```
422
+ *
423
+ * @example Full example with both
424
+ * ```ts
425
+ * const { UserServiceError } = defineError('UserServiceError')
426
+ * .withContext<{ userId: string }>()
427
+ * .withCause<RepoError | undefined>()
428
+ *
429
+ * // Type extraction works
430
+ * type UserServiceError = ReturnType<typeof UserServiceError>
431
+ * ```
432
+ */
433
+ declare function defineError<TName extends `${string}Error`>(name: TName): ErrorBuilder<TName>;
281
434
  //#endregion
282
- export { AnyTaggedError, TaggedError, createTaggedError, extractErrorMessage };
435
+ export { AnyTaggedError, TaggedError, createTaggedError, defineError, extractErrorMessage };
283
436
  //# 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":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAGY,KAAA,cAAA,GAAc;EAUrB,IAAA,EAAA,MAAA;EAAW,OAAA,EAAA,MAAA;CAAA;;;;;;AAIO;AAAA;;KAJlB,WAgBqB,CAAA,QAAA,CAAA,GAAA,CAhBI,QAgBJ,CAAA,SAAA,CAAA,SAAA,CAAA,GAAA;EAAM,OACnB,CAAA,EAhBE,MAgBF,CAAA,MAAA,EAAA,OAAA,CAAA;CAAc,GAAA,CAAA,SACH,CAAA,SAAA,CAhBA,QAgBA,CAAA,GAAA;EAAM,OACR,CAAA,EAhBN,OAgBM,CAhBE,QAgBF,EAAA,SAAA,CAAA;CAAM,GAAA;EAAP,OACP,EAhBC,QAgBD;AAAM,CAAA;AAiEpB;;;;;;;;;;AAIY,KAzEP,SAyEO,CAAA,MAAA,CAAA,GAAA,CAzEc,MAyEd,CAAA,SAAA,CAAA,SAAA,CAAA,GAAA;UAxEC;yBACW;UACV,QAAQ;ACMtB,CAAA,GAAgB;EAgEX,KAAA,CAAA,EDrES,MCqET;CAAmB;;;AACmB;AAAA;;;;;;;;;AAae;AAAA;;;;;;;;;;;;AAaI;AAAA;;;;;;;;;;;;;;;AAcH;AAAA;;;;;;AAmB1C;AAAA;;;;;;;AAUR;AAAA;;;;;;AAiBgB,KD3Fb,WC2Fa,CAAA,cAAA,MAAA,GAAA,MAAA,EAAA,iBDzFP,MCyFO,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,SAAA,GAAA,SAAA,EAAA,eDxFT,cCwFS,GAAA,SAAA,GAAA,SAAA,CAAA,GDvFrB,QCuFqB,CAAA;EAAQ,IAA3B,EDrFE,KCqFF;EAAW,OAAA,EAAA,MAAA;AAAA,CAAA,GDnFZ,WCyFA,CDzFY,QCyFZ,CAAA,GDxFH,SCwF6B,CDxFnB,MCwFmB,CAAA,CAAA;;;;ADhM/B;AAA+D;;;;;;;;AAcxC;AAAA;;;;;;;;AAgBH;AAiEpB;;;;;;;;;;AAIY;;;;AChEZ;AAgEK,iBAhEW,mBAAA,CAgEQ,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA;;;;AACmB;AAAA;;;;;;;;KADtC,mBAc+B,CAAA,UAAA,GAAA,MAAA,OAAA,CAAA,GAbnC,CAamC,SAAA,GAAA,KAAA,MAAA,OAAA,GAAA,GAbE,KAaF,KAAA,GAAA,KAAA;AAAsB;AAAA;;;KAHrD,iBAcE,CAAA,cAAA,GAAA,MAAA,OAAA,CAAA,GAAA,QAbA,KAaqC,GAb7B,wBAa6B,CAbJ,CAaI,CAAA,EAAC,GAAA,QAXtC,mBAWQ,CAXY,KAWZ,CAAA,GAXqB,sBAWrB,CAX4C,KAW5C,CAAA,EAA4B;;;;;AAEmB,KANzD,qBAMyD,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAJ5C,MAI4C,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,GAAA,QAFvD,KASF,GATU,4BASQ,CATqB,CASrB,EATwB,QASxB,CAAA,EAAA,GAAA,QAPhB,mBASW,CATS,KAST,CAAA,GATkB,0BASlB,CAT6C,KAS7C,EAToD,QASpD,CAAA,EAAM;;;;;KAFnB,kBAKU,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAHG,MAGH,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,eAFC,cAED,CAAA,GAAA,QAAR,KAEoB,GAFZ,yBAEY,CAFc,CAEd,EAFiB,QAEjB,EAF2B,MAE3B,CAAA,EAAK,GAAA,QAAzB,mBACL,CADyB,KACzB,CAAA,GADkC,uBAClC,CAAA,KAAA,EACA,QADA,EAEA,MAFA,CAAA,EAAK;;;AADoD;AAAA;KAetD,wBAAwB,CAAA,cAAA,MAAA,CAAA,GAAA,CAAA,KAAA,EAAA;EAAA,OAElB,EAAA,MAAA;EAAM,OACR,CAAA,EADE,MACF,CAAA,MAAA,EAAA,OAAA,CAAA;EAAc,KACL,CAAA,EADT,cACS;CAAK,EAAA,GAAjB,WAAA,CAAY,KAAZ,CAAA;AAAW;AAAA;;;KAMZ,sBAGI,CAAA,cAAA,MAAA,CAAA,GAAA,CAAA,KAAA,EAAA;EAAc,OACD,EAAA,MAAA;EAAK,OAAjB,CAAA,EAFC,MAED,CAAA,MAAA,EAAA,OAAA,CAAA;EAAW,KAAf,CAAA,EADG,cACH;AAAG,CAAA,EAAA,GAAH,GAAG,CAAC,WAAD,CAAa,KAAb,CAAA,CAAA;AAAA;;;;KAUJ,4BAMI,CAAA,cAAA,MAAA,EAAA,iBAJS,MAIT,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,GAAA,CAAA,KAAA,EAAA;EAAc,OACL,EAAA,MAAA;EAAK,OAAE,EAFf,QAEe;EAAQ,KAA3B,CAAA,EADG,cACH;AAAW,CAAA,EAAA,GAAX,WAAW,CAAC,KAAD,EAAQ,QAAR,CAAA;AAAA;;;;KAMZ,0BAMI,CAAA,cAAA,MAAA,EAAA,iBAJS,MAIT,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,GAAA,CAAA,KAAA,EAAA;EAAc,OACD,EAAA,MAAA;EAAK,OAAE,EAFnB,QAEmB;EAAQ,KAA3B,CAAA,EADD,cACC;CAAW,EAAA,GAAf,GAAA,CAAI,WAAJ,CAAgB,KAAhB,EAAuB,QAAvB,CAAA,CAAA;AAAG;AAAA;;;KAUJ,yBAGW,CAAA,cAAA,MAAA,EAAA,iBADE,MACF,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,eAAA,cAAA,CAAA,GAAA,CAAA,KAAA,EAAA;EAAc,OAGpB,EAAA,MAAA;EAAQ,OACT,EADC,QACD;EAAM,KACG,CAAA,EADT,MACS;CAAK,EAAA,GAAjB,WAAmB,CAAP,KAAO,EAAA,QAAA,EAAU,MAAV,CAAA;;;AAAR;AAAA;KAMZ,uBAAuB,CAAA,cAAA,MAAA,EAAA,iBAEV,MAFU,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,eAGZ,cAHY,CAAA,GAAA,CAAA,KAAA,EAAA;EAAA,OAEV,EAAA,MAAA;EAAM,OACR,EAGN,QAHM;EAAc,KAGpB,CAAA,EACD,MADC;CAAQ,EAAA,GAEZ,GADG,CACC,WADD,CACa,KADb,EACoB,QADpB,EAC8B,MAD9B,CAAA,CAAA;;;;;;AACA;AAiET;;;;;AAEoB;AAGpB;;;;;;;AAGqC;AAGrC;;;;;;;;;AAIkC;AAA0B;;;;;;;;AAsCzC;AAAA;;;;;;;;;;;;AAaI;AAAA;;;;;;AAWO,iBA7Ed,iBA6Ec,CAAA,cAAA,GAAA,MAAA,OAAA,CAAA,CAAA,IAAA,EA5EvB,KA4EuB,CAAA,EA3E3B,iBA2E2B,CA3ET,KA2ES,CAAA;AAArB,iBAxEO,iBAwEP,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAtES,MAsET,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,IAAA,EArED,KAqEC,CAAA,EArEO,qBAqEP,CArE6B,KAqE7B,EArEoC,QAqEpC,CAAA;AACS,iBAnEF,iBAmEE,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAjEA,MAiEA,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,eAhEF,cAgEE,CAAA,CAAA,IAAA,EA/DV,KA+DU,CAAA,EA/DF,kBA+DE,CA/DiB,KA+DjB,EA/DwB,QA+DxB,EA/DkC,MA+DlC,CAAA;;;;;;KA3Bb,mBA8Be,CAAA,CAAA,EAAA,aAAA,MAAA,CAAA,GAAA,SAAA,SA9BiD,CA8BjD,GAAA,QA7BT,IA6BmB,IA7BX,OA6BW,CA7BH,CA6BG,EAAA,SAAA,CAAA,EAAM,GAAA,QA5BzB,IA6BW,GA7BJ,CA6BI,EAAK;;;;AAAlB,KAxBJ,UAwBI,CAAA,iBAvBS,MAuBT,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,SAAA,EAAA,eAtBO,cAsBP,GAAA,SAAA,CAAA,GAAA;EAOJ,OAAA,EAAA,MAAY;CAAA,GAAA,CA5BU,QA4BV,SAAA,SAAA,GAAA;EAAA,OAEC,CAAA,EA7BH,MA6BG,CAAA,MAAA,EAAA,OAAA,CAAA;CAAM,GA5BrB,mBA6Ba,CA7BO,QA6BP,EAAA,SAAA,CAAA,CAAA,GAAA,CA5Bd,MA4Bc,SAAA,SAAA,GAAA;EAAc,KACX,CAAA,EA5BL,cA4BK;CAAK,GA3BpB,mBA2BsB,CA3BF,MA2BE,EAAA,OAAA,CAAA,CAAA;;;;KAtBrB,cAmDH,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAjDgB,MAiDhB,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,SAAA,EAAA,eAhDc,cAgDd,GAAA,SAAA,CAAA,GAAA,QA9CK,KA+CL,GAAA,CAAA,KAAA,EA9CO,UA8CP,CA9CkB,QA8ClB,EA9C4B,MA8C5B,CAAA,EAAA,GA7CI,WA6CJ,CA7CgB,KA6ChB,EA7CuB,QA6CvB,EA7CiC,MA6CjC,CAAA,EAAC,GAAA,QA3CI,mBAyCwD,CAzCpC,KAyCoC,CAAA,GAAA,CAAA,KAAA,EAxCtD,UAwCsD,CAxC3C,QAwC2C,EAxCjC,MAwCiC,CAAA,EAAA,GAvCzD,GAuCyD,CAvCrD,WAuCqD,CAvCzC,KAuCyC,EAvClC,QAuCkC,EAvCxB,MAuCwB,CAAA,CAAA,EAAY;;;;;AAmCX,KAnE3D,YAmE2D,CAAA,cAAA,GAAA,MAAA,OAAA,EAAA,iBAjE9C,MAiE8C,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,SAAA,GAAA,SAAA,EAAA,eAhEhD,cAgEgD,GAAA,SAAA,GAAA,SAAA,CAAA,GA/D5D,cA+D4D,CA/D7C,KA+D6C,EA/DtC,QA+DsC,EA/D5B,MA+D4B,CAAA,GAAA;EAqEhD;;;;;AAED;;;;;;;;;;;;;;;;;;;;;;wBA1GQ,wCAAwC,aAC7D,OACA,GACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAgCmB,+BAA+B,aAClD,OACA,UACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkEc,kDACT,QACJ,aAAa"}
@@ -75,7 +75,86 @@ function createTaggedError(name) {
75
75
  [errName]: errConstructor
76
76
  };
77
77
  }
78
+ /**
79
+ * Defines a new tagged error type with a fluent builder API.
80
+ *
81
+ * Returns an object containing:
82
+ * - `{Name}Error`: Factory function that creates plain TaggedError objects
83
+ * - `{Name}Err`: Factory function that creates Err-wrapped TaggedError objects
84
+ * - `withContext<T>()`: Chain method to constrain context type
85
+ * - `withCause<T>()`: Chain method to constrain cause type
86
+ *
87
+ * **Default behavior (no chaining):**
88
+ * - `context` is optional and accepts any `Record<string, unknown>`
89
+ * - `cause` is optional and accepts any `AnyTaggedError`
90
+ *
91
+ * **Optionality via type unions:**
92
+ * Both `withContext` and `withCause` determine optionality based on whether
93
+ * the type includes `undefined`:
94
+ * - `T` without undefined → property is required
95
+ * - `T | undefined` → property is optional but typed when provided
96
+ *
97
+ * @template TName - The name of the error type (must end with "Error")
98
+ * @param name - The name of the error type
99
+ *
100
+ * @example Simple error (flexible mode)
101
+ * ```ts
102
+ * const { NetworkError, NetworkErr } = defineError('NetworkError')
103
+ *
104
+ * NetworkError({ message: 'Connection failed' })
105
+ * NetworkError({ message: 'Timeout', context: { url: 'https://...' } })
106
+ * ```
107
+ *
108
+ * @example Required context
109
+ * ```ts
110
+ * const { ApiError, ApiErr } = defineError('ApiError')
111
+ * .withContext<{ endpoint: string; status: number }>()
112
+ *
113
+ * ApiError({ message: 'Failed', context: { endpoint: '/users', status: 500 } })
114
+ * // ApiError({ message: 'Failed' }) // Type error: context required
115
+ * ```
116
+ *
117
+ * @example Optional typed cause
118
+ * ```ts
119
+ * const { ServiceError } = defineError('ServiceError')
120
+ * .withCause<DbError | CacheError | undefined>()
121
+ *
122
+ * ServiceError({ message: 'Failed' }) // OK
123
+ * ServiceError({ message: 'Failed', cause: dbError }) // OK, typed
124
+ * ```
125
+ *
126
+ * @example Full example with both
127
+ * ```ts
128
+ * const { UserServiceError } = defineError('UserServiceError')
129
+ * .withContext<{ userId: string }>()
130
+ * .withCause<RepoError | undefined>()
131
+ *
132
+ * // Type extraction works
133
+ * type UserServiceError = ReturnType<typeof UserServiceError>
134
+ * ```
135
+ */
136
+ function defineError(name) {
137
+ const createBuilder = () => {
138
+ const errorConstructor = (input) => ({
139
+ name,
140
+ ...input
141
+ });
142
+ const errName = name.replace(/Error$/, "Err");
143
+ const errConstructor = (input) => Err(errorConstructor(input));
144
+ return {
145
+ [name]: errorConstructor,
146
+ [errName]: errConstructor,
147
+ withContext() {
148
+ return createBuilder();
149
+ },
150
+ withCause() {
151
+ return createBuilder();
152
+ }
153
+ };
154
+ };
155
+ return createBuilder();
156
+ }
78
157
 
79
158
  //#endregion
80
- export { createTaggedError, extractErrorMessage };
159
+ export { createTaggedError, defineError, extractErrorMessage };
81
160
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
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, AnyTaggedError } 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 * 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 (flexible mode).\n * Context and cause are optional with loose typing.\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.\n * Context is required with exact type; cause is optional.\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 * Context is required; cause is optional but constrained to specific type.\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 (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with flexible context and cause.\n * Single signature: context and cause are optional with loose typing.\n */\ntype FlexibleErrorConstructor<TName extends string> = (input: {\n\tmessage: string;\n\tcontext?: Record<string, unknown>;\n\tcause?: AnyTaggedError;\n}) => TaggedError<TName>;\n\n/**\n * Creates Err-wrapped TaggedError objects with flexible context and cause.\n * Single signature: context and cause are optional with loose typing.\n */\ntype FlexibleErrConstructor<TName extends string> = (input: {\n\tmessage: string;\n\tcontext?: Record<string, unknown>;\n\tcause?: AnyTaggedError;\n}) => Err<TaggedError<TName>>;\n\n// =============================================================================\n// Context-Fixed Mode Constructor Types (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with fixed context.\n * Single signature: context is required, cause is optional.\n */\ntype ContextFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: AnyTaggedError;\n}) => TaggedError<TName, TContext>;\n\n/**\n * Creates Err-wrapped TaggedError objects with fixed context.\n * Single signature: context is required, cause is optional.\n */\ntype ContextFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: AnyTaggedError;\n}) => Err<TaggedError<TName, TContext>>;\n\n// =============================================================================\n// Both-Fixed Mode Constructor Types (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with both context and cause fixed.\n * Single signature: context is required, cause is optional but constrained.\n */\ntype BothFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: TCause;\n}) => TaggedError<TName, TContext, TCause>;\n\n/**\n * Creates Err-wrapped TaggedError objects with both context and cause fixed.\n * Single signature: context is required, cause is optional but constrained.\n */\ntype BothFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: TCause;\n}) => Err<TaggedError<TName, TContext, TCause>>;\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, loosely typed\n * 2. **Fixed context mode** (TContext specified): Context is required with exact shape\n * 3. **Both fixed mode** (TContext + TCause): Context required, cause constrained\n *\n * **ReturnType works correctly in all modes:**\n * ```ts\n * const { NetworkError } = createTaggedError('NetworkError');\n * type NetworkError = ReturnType<typeof NetworkError>;\n * // = TaggedError<'NetworkError'> with optional context/cause\n * ```\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 and cause optional, loosely typed\n * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');\n * NetworkError({ message: 'Connection failed' });\n * NetworkError({ message: 'Timeout', context: { url: 'https://...' } });\n * NetworkError({ message: 'Failed', cause: otherError });\n *\n * // Type annotation works with ReturnType:\n * type NetworkError = ReturnType<typeof NetworkError>;\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 = AnyTaggedError,\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"],"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;AA2ND,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"}
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}","input: ErrorInput<TContext, TCause>"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type { TaggedError, AnyTaggedError } 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 * 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 (flexible mode).\n * Context and cause are optional with loose typing.\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.\n * Context is required with exact type; cause is optional.\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 * Context is required; cause is optional but constrained to specific type.\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 (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with flexible context and cause.\n * Single signature: context and cause are optional with loose typing.\n */\ntype FlexibleErrorConstructor<TName extends string> = (input: {\n\tmessage: string;\n\tcontext?: Record<string, unknown>;\n\tcause?: AnyTaggedError;\n}) => TaggedError<TName>;\n\n/**\n * Creates Err-wrapped TaggedError objects with flexible context and cause.\n * Single signature: context and cause are optional with loose typing.\n */\ntype FlexibleErrConstructor<TName extends string> = (input: {\n\tmessage: string;\n\tcontext?: Record<string, unknown>;\n\tcause?: AnyTaggedError;\n}) => Err<TaggedError<TName>>;\n\n// =============================================================================\n// Context-Fixed Mode Constructor Types (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with fixed context.\n * Single signature: context is required, cause is optional.\n */\ntype ContextFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: AnyTaggedError;\n}) => TaggedError<TName, TContext>;\n\n/**\n * Creates Err-wrapped TaggedError objects with fixed context.\n * Single signature: context is required, cause is optional.\n */\ntype ContextFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: AnyTaggedError;\n}) => Err<TaggedError<TName, TContext>>;\n\n// =============================================================================\n// Both-Fixed Mode Constructor Types (SIMPLIFIED - no overloads)\n// =============================================================================\n\n/**\n * Creates plain TaggedError objects with both context and cause fixed.\n * Single signature: context is required, cause is optional but constrained.\n */\ntype BothFixedErrorConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: TCause;\n}) => TaggedError<TName, TContext, TCause>;\n\n/**\n * Creates Err-wrapped TaggedError objects with both context and cause fixed.\n * Single signature: context is required, cause is optional but constrained.\n */\ntype BothFixedErrConstructor<\n\tTName extends string,\n\tTContext extends Record<string, unknown>,\n\tTCause extends AnyTaggedError,\n> = (input: {\n\tmessage: string;\n\tcontext: TContext;\n\tcause?: TCause;\n}) => Err<TaggedError<TName, TContext, TCause>>;\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 * @deprecated Use `defineError()` instead for a cleaner fluent API:\n * ```ts\n * // Before\n * const { FileError } = createTaggedError<'FileError', { path: string }>('FileError')\n *\n * // After\n * const { FileError } = defineError('FileError')\n * .withContext<{ path: string }>()\n * ```\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, loosely typed\n * 2. **Fixed context mode** (TContext specified): Context is required with exact shape\n * 3. **Both fixed mode** (TContext + TCause): Context required, cause constrained\n *\n * **ReturnType works correctly in all modes:**\n * ```ts\n * const { NetworkError } = createTaggedError('NetworkError');\n * type NetworkError = ReturnType<typeof NetworkError>;\n * // = TaggedError<'NetworkError'> with optional context/cause\n * ```\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 and cause optional, loosely typed\n * const { NetworkError, NetworkErr } = createTaggedError('NetworkError');\n * NetworkError({ message: 'Connection failed' });\n * NetworkError({ message: 'Timeout', context: { url: 'https://...' } });\n * NetworkError({ message: 'Failed', cause: otherError });\n *\n * // Type annotation works with ReturnType:\n * type NetworkError = ReturnType<typeof NetworkError>;\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 = AnyTaggedError,\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// =============================================================================\n// Fluent API Types\n// =============================================================================\n\n/**\n * Helper type that determines optionality based on whether T includes undefined.\n * - If T includes undefined → property is optional\n * - If T does not include undefined → property is required\n */\ntype OptionalIfUndefined<T, TKey extends string> = undefined extends T\n\t? { [K in TKey]?: Exclude<T, undefined> }\n\t: { [K in TKey]: T };\n\n/**\n * Input type for error constructors with fluent API context/cause handling.\n */\ntype ErrorInput<\n\tTContext extends Record<string, unknown> | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = { message: string } & (TContext extends undefined\n\t? { context?: Record<string, unknown> }\n\t: OptionalIfUndefined<TContext, \"context\">) &\n\t(TCause extends undefined\n\t\t? { cause?: AnyTaggedError }\n\t\t: OptionalIfUndefined<TCause, \"cause\">);\n\n/**\n * The factories object returned by defineError and its builder methods.\n */\ntype ErrorFactories<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown> | undefined,\n\tTCause extends AnyTaggedError | undefined,\n> = {\n\t[K in TName]: (\n\t\tinput: ErrorInput<TContext, TCause>,\n\t) => TaggedError<TName, TContext, TCause>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: (\n\t\tinput: ErrorInput<TContext, TCause>,\n\t) => Err<TaggedError<TName, TContext, TCause>>;\n};\n\n/**\n * Builder interface for the fluent defineError API.\n * Provides chaining methods and the error factories.\n */\ntype ErrorBuilder<\n\tTName extends `${string}Error`,\n\tTContext extends Record<string, unknown> | undefined = undefined,\n\tTCause extends AnyTaggedError | undefined = undefined,\n> = ErrorFactories<TName, TContext, TCause> & {\n\t/**\n\t * Constrains the context type for this error.\n\t *\n\t * Optionality is determined by whether the type includes `undefined`:\n\t * - `withContext<T>()` where T doesn't include undefined → context is **required**\n\t * - `withContext<T | undefined>()` → context is **optional** but typed when provided\n\t *\n\t * @typeParam T - The shape of the context object. Include `| undefined` to make optional.\n\t *\n\t * @example Required context\n\t * ```ts\n\t * const { FileError } = defineError('FileError')\n\t * .withContext<{ path: string }>()\n\t *\n\t * FileError({ message: 'Not found', context: { path: '/etc/config' } }) // OK\n\t * FileError({ message: 'Not found' }) // Type error: context required\n\t * ```\n\t *\n\t * @example Optional but typed context\n\t * ```ts\n\t * const { LogError } = defineError('LogError')\n\t * .withContext<{ file: string; line: number } | undefined>()\n\t *\n\t * LogError({ message: 'Parse error' }) // OK\n\t * LogError({ message: 'Parse error', context: { file: 'app.ts', line: 42 } }) // OK\n\t * ```\n\t */\n\twithContext<T extends Record<string, unknown> | undefined>(): ErrorBuilder<\n\t\tTName,\n\t\tT,\n\t\tTCause\n\t>;\n\n\t/**\n\t * Constrains the cause type for this error.\n\t *\n\t * Optionality is determined by whether the type includes `undefined`:\n\t * - `withCause<T>()` where T doesn't include undefined → cause is **required**\n\t * - `withCause<T | undefined>()` → cause is **optional** but typed when provided\n\t *\n\t * Since cause is typically optional, include `| undefined` in most cases.\n\t *\n\t * @typeParam T - The allowed cause type(s). Include `| undefined` to make optional.\n\t *\n\t * @example Optional typed cause (common)\n\t * ```ts\n\t * const { ServiceError } = defineError('ServiceError')\n\t * .withCause<DbError | CacheError | undefined>()\n\t *\n\t * ServiceError({ message: 'Failed' }) // OK\n\t * ServiceError({ message: 'Failed', cause: dbError }) // OK\n\t * ```\n\t *\n\t * @example Required cause (for wrapper errors)\n\t * ```ts\n\t * const { UnhandledError } = defineError('UnhandledError')\n\t * .withCause<AnyTaggedError>()\n\t *\n\t * UnhandledError({ message: 'Unexpected', cause: originalError }) // OK\n\t * UnhandledError({ message: 'Unexpected' }) // Type error: cause required\n\t * ```\n\t */\n\twithCause<T extends AnyTaggedError | undefined>(): ErrorBuilder<\n\t\tTName,\n\t\tTContext,\n\t\tT\n\t>;\n};\n\n// =============================================================================\n// Fluent API Implementation\n// =============================================================================\n\n/**\n * Defines a new tagged error type with a fluent builder API.\n *\n * Returns an object containing:\n * - `{Name}Error`: Factory function that creates plain TaggedError objects\n * - `{Name}Err`: Factory function that creates Err-wrapped TaggedError objects\n * - `withContext<T>()`: Chain method to constrain context type\n * - `withCause<T>()`: Chain method to constrain cause type\n *\n * **Default behavior (no chaining):**\n * - `context` is optional and accepts any `Record<string, unknown>`\n * - `cause` is optional and accepts any `AnyTaggedError`\n *\n * **Optionality via type unions:**\n * Both `withContext` and `withCause` determine optionality based on whether\n * the type includes `undefined`:\n * - `T` without undefined → property is required\n * - `T | undefined` → property is optional but typed when provided\n *\n * @template TName - The name of the error type (must end with \"Error\")\n * @param name - The name of the error type\n *\n * @example Simple error (flexible mode)\n * ```ts\n * const { NetworkError, NetworkErr } = defineError('NetworkError')\n *\n * NetworkError({ message: 'Connection failed' })\n * NetworkError({ message: 'Timeout', context: { url: 'https://...' } })\n * ```\n *\n * @example Required context\n * ```ts\n * const { ApiError, ApiErr } = defineError('ApiError')\n * .withContext<{ endpoint: string; status: number }>()\n *\n * ApiError({ message: 'Failed', context: { endpoint: '/users', status: 500 } })\n * // ApiError({ message: 'Failed' }) // Type error: context required\n * ```\n *\n * @example Optional typed cause\n * ```ts\n * const { ServiceError } = defineError('ServiceError')\n * .withCause<DbError | CacheError | undefined>()\n *\n * ServiceError({ message: 'Failed' }) // OK\n * ServiceError({ message: 'Failed', cause: dbError }) // OK, typed\n * ```\n *\n * @example Full example with both\n * ```ts\n * const { UserServiceError } = defineError('UserServiceError')\n * .withContext<{ userId: string }>()\n * .withCause<RepoError | undefined>()\n *\n * // Type extraction works\n * type UserServiceError = ReturnType<typeof UserServiceError>\n * ```\n */\nexport function defineError<TName extends `${string}Error`>(\n\tname: TName,\n): ErrorBuilder<TName> {\n\tconst createBuilder = <\n\t\tTContext extends Record<string, unknown> | undefined = undefined,\n\t\tTCause extends AnyTaggedError | undefined = undefined,\n\t>(): ErrorBuilder<TName, TContext, TCause> => {\n\t\tconst errorConstructor = (input: ErrorInput<TContext, TCause>) =>\n\t\t\t({ name, ...input }) as unknown as TaggedError<TName, TContext, TCause>;\n\n\t\tconst errName = name.replace(\n\t\t\t/Error$/,\n\t\t\t\"Err\",\n\t\t) as ReplaceErrorWithErr<TName>;\n\t\tconst errConstructor = (input: ErrorInput<TContext, TCause>) =>\n\t\t\tErr(errorConstructor(input));\n\n\t\treturn {\n\t\t\t[name]: errorConstructor,\n\t\t\t[errName]: errConstructor,\n\t\t\twithContext<T extends Record<string, unknown> | undefined>() {\n\t\t\t\treturn createBuilder<T, TCause>();\n\t\t\t},\n\t\t\twithCause<T extends AnyTaggedError | undefined>() {\n\t\t\t\treturn createBuilder<TContext, T>();\n\t\t\t},\n\t\t} as ErrorBuilder<TName, TContext, TCause>;\n\t};\n\n\treturn createBuilder();\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;AAqOD,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyLD,SAAgB,YACfD,MACsB;CACtB,MAAM,gBAAgB,MAGwB;EAC7C,MAAM,mBAAmB,CAACE,WACxB;GAAE;GAAM,GAAG;EAAO;EAEpB,MAAM,UAAU,KAAK,QACpB,UACA,MACA;EACD,MAAM,iBAAiB,CAACA,UACvB,IAAI,iBAAiB,MAAM,CAAC;AAE7B,SAAO;IACL,OAAO;IACP,UAAU;GACX,cAA6D;AAC5D,WAAO,eAA0B;GACjC;GACD,YAAkD;AACjD,WAAO,eAA4B;GACnC;EACD;CACD;AAED,QAAO,eAAe;AACtB"}
@@ -23,8 +23,12 @@ type DefineQueryInput<TQueryFnData = unknown, TError = DefaultError, TData = TQu
23
23
  /**
24
24
  * Output of defineQuery function.
25
25
  *
26
+ * The query definition is directly callable and defaults to `ensure()` behavior,
27
+ * which is recommended for most imperative use cases like preloaders.
28
+ *
26
29
  * Provides both reactive and imperative interfaces for data fetching:
27
- * - `options()`: Returns config for use with useQuery() or createQuery()
30
+ * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not
31
+ * - `options`: Returns config for use with useQuery() or createQuery()
28
32
  * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)
29
33
  * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
30
34
  *
@@ -32,9 +36,24 @@ type DefineQueryInput<TQueryFnData = unknown, TError = DefaultError, TData = TQu
32
36
  * @template TError - The type of error that can be thrown
33
37
  * @template TData - The type of data returned by the query (after select transform)
34
38
  * @template TQueryKey - The type of the query key
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const userQuery = defineQuery({...});
43
+ *
44
+ * // Directly callable (same as .ensure())
45
+ * const { data, error } = await userQuery();
46
+ *
47
+ * // Or use explicit methods
48
+ * const { data, error } = await userQuery.ensure();
49
+ * const { data, error } = await userQuery.fetch();
50
+ *
51
+ * // For reactive usage
52
+ * const query = createQuery(userQuery.options);
53
+ * ```
35
54
  */
36
- type DefineQueryOutput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = {
37
- options: () => QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
55
+ type DefineQueryOutput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = (() => Promise<Result<TQueryData, TError>>) & {
56
+ options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
38
57
  fetch: () => Promise<Result<TQueryData, TError>>;
39
58
  ensure: () => Promise<Result<TQueryData, TError>>;
40
59
  };
@@ -57,17 +76,35 @@ type DefineMutationInput<TData, TError, TVariables = void, TContext = unknown> =
57
76
  /**
58
77
  * Output of defineMutation function.
59
78
  *
79
+ * The mutation definition is directly callable, which executes the mutation
80
+ * and returns a Result. This is equivalent to calling `.execute()`.
81
+ *
60
82
  * Provides both reactive and imperative interfaces for data mutations:
61
- * - `options()`: Returns config for use with useMutation() or createMutation()
62
- * - `execute()`: Directly executes the mutation and returns a Result
83
+ * - `(variables)` (callable): Same as `execute()` - directly executes the mutation
84
+ * - `options`: Returns config for use with useMutation() or createMutation()
85
+ * - `execute(variables)`: Directly executes the mutation and returns a Result
63
86
  *
64
87
  * @template TData - The type of data returned by the mutation
65
88
  * @template TError - The type of error that can be thrown
66
89
  * @template TVariables - The type of variables passed to the mutation
67
90
  * @template TContext - The type of context data for optimistic updates
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const createUser = defineMutation({...});
95
+ *
96
+ * // Directly callable (same as .execute())
97
+ * const { data, error } = await createUser({ name: 'John' });
98
+ *
99
+ * // Or use explicit method
100
+ * const { data, error } = await createUser.execute({ name: 'John' });
101
+ *
102
+ * // For reactive usage
103
+ * const mutation = createMutation(createUser.options);
104
+ * ```
68
105
  */
69
- type DefineMutationOutput<TData, TError, TVariables = void, TContext = unknown> = {
70
- options: () => MutationOptions<TData, TError, TVariables, TContext>;
106
+ type DefineMutationOutput<TData, TError, TVariables = void, TContext = unknown> = ((variables: TVariables) => Promise<Result<TData, TError>>) & {
107
+ options: MutationOptions<TData, TError, TVariables, TContext>;
71
108
  execute: (variables: TVariables) => Promise<Result<TData, TError>>;
72
109
  };
73
110
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/query/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAwBA;;;;;;;;;AAO4C,KAPhC,gBAOgC,CAAA,eAAA,OAAA,EAAA,SALlC,YAKkC,EAAA,QAJnC,YAImC,EAAA,aAH9B,YAG8B,EAAA,kBAFzB,QAEyB,GAFd,QAEc,CAAA,GADxC,IACwC,CAA3C,oBAA2C,CAAtB,YAAsB,EAAR,MAAQ,EAAA,KAAA,EAAO,UAAP,EAAmB,SAAnB,CAAA,EAAA,SAAA,CAAA,GAAA;EAAK,QAAE,EAGxC,SAHwC;EAAU,aAAE,EAI/C,aAJ+C,CAIjC,MAJiC,CAI1B,YAJ0B,EAIZ,MAJY,CAAA,EAIH,SAJG,CAAA;CAAS;;;;;;;;AAI3C;AAgB7B;;;;;AAKmB,KALP,iBAKO,CAAA,eAAA,OAAA,EAAA,SAHT,YAGS,EAAA,QAFV,YAEU,EAAA,aADL,YACK,EAAA,kBAAA,QAAA,GAAW,QAAX,CAAA,GAAA;EAAQ,OAAG,EAAA,GAAA,GAEd,oBAFc,CAG5B,YAH4B,EAI5B,MAJ4B,EAK5B,KAL4B,EAM5B,UAN4B,EAO5B,SAP4B,CAAA;EAAQ,KAGpC,EAAA,GAAA,GAMY,OANZ,CAMoB,MANpB,CAM2B,UAN3B,EAMuC,MANvC,CAAA,CAAA;EAAY,MACZ,EAAA,GAAA,GAMa,OANb,CAMqB,MANrB,CAM4B,UAN5B,EAMwC,MANxC,CAAA,CAAA;CAAM;;;;;;;;;;;;AAMc;AAeV,KAAA,mBAAmB,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAK3B,IAL2B,CAKtB,eALsB,CAKN,KALM,EAKC,MALD,EAKS,UALT,EAKqB,QALrB,CAAA,EAAA,YAAA,CAAA,GAAA;EAAA,WAAA,EAMjB,WANiB;EAAA,gBAKN,EAEN,gBAFM,CAEW,MAFX,CAEkB,KAFlB,EAEyB,MAFzB,CAAA,EAEkC,UAFlC,CAAA;CAAK;;;;;;;;;;;AAEK;AAenC;AAAgC,KAApB,oBAAoB,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAAA;EAAA,OAMA,EAAA,GAAA,GAAhB,eAAgB,CAAA,KAAA,EAAO,MAAP,EAAe,UAAf,EAA2B,QAA3B,CAAA;EAAK,OAAE,EAAA,CAAA,SAAA,EACjB,UADiB,EAAA,GACF,OADE,CACM,MADN,CACa,KADb,EACoB,MADpB,CAAA,CAAA;CAAM;;;;;;;;AACD;AA4C5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoQwB;;;;iBApQR,oBAAA,cAAkC;iDA8D1C,eACD,2BACK,gCACQ,wCAET,iBACR,cACA,QACA,OACA,YACA,eAEC,kBAAkB,cAAc,QAAQ,OAAO,YAAY;kFAyLpD,oBAAoB,OAAO,QAAQ,YAAY,cACtD,qBAAqB,OAAO,QAAQ,YAAY"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/query/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAwBA;;;;;;;;;AAO4C,KAPhC,gBAOgC,CAAA,eAAA,OAAA,EAAA,SALlC,YAKkC,EAAA,QAJnC,YAImC,EAAA,aAH9B,YAG8B,EAAA,kBAFzB,QAEyB,GAFd,QAEc,CAAA,GADxC,IACwC,CAA3C,oBAA2C,CAAtB,YAAsB,EAAR,MAAQ,EAAA,KAAA,EAAO,UAAP,EAAmB,SAAnB,CAAA,EAAA,SAAA,CAAA,GAAA;EAAK,QAAE,EAGxC,SAHwC;EAAU,aAAE,EAI/C,aAJ+C,CAIjC,MAJiC,CAI1B,YAJ0B,EAIZ,MAJY,CAAA,EAIH,SAJG,CAAA;CAAS;;;;;;;;AAI3C;AAmC7B;;;;;;;;;;;;;;;;;;;;;;;;AAee,KAfH,iBAeG,CAAA,eAAA,OAAA,EAAA,SAbL,YAaK,EAAA,QAZN,YAYM,EAAA,aAXD,YAWC,EAAA,kBAVI,QAUJ,GAVe,QAUf,CAAA,GAAA,CAAA,GAAA,GATJ,OASI,CATI,MASJ,CATW,UASX,EATuB,MASvB,CAAA,CAAA,CAAA,GAAA;EAAO,OAAA,EARZ,oBAQY,CAPpB,YAOoB,EANpB,MAMoB,EALpB,KAKoB,EAJpB,UAIoB,EAHpB,SAGoB,CAAA;EAeV,KAAA,EAAA,GAAA,GAhBE,OAgBF,CAhBU,MAgBS,CAhBF,UAgBE,EAhBU,MAgBV,CAAA,CAAA;EAAA,MAAA,EAAA,GAAA,GAfhB,OAegB,CAfR,MAeQ,CAfD,UAeC,EAfW,MAeX,CAAA,CAAA;CAAA;;;;;;;;;;;;AAOI;AAiCvB,KAxCA,mBAwCoB,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAnC5B,IAmC4B,CAnCvB,eAmCuB,CAnCP,KAmCO,EAnCA,MAmCA,EAnCQ,UAmCR,EAnCoB,QAmCpB,CAAA,EAAA,YAAA,CAAA,GAAA;EAAA,WAAA,EAlClB,WAkCkB;EAAA,gBAKf,EAtCE,gBAsCF,CAtCmB,MAsCnB,CAtC0B,KAsC1B,EAtCiC,MAsCjC,CAAA,EAtC0C,UAsC1C,CAAA;CAAU;;;;;;;;;;;;;;AAEiB;AA4C5C;;;;;;;;;;;;;;;;AA+EmD,KAlIvC,oBAkIuC,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAAA,CAAA,CAAA,SAAA,EA7HlC,UA6HkC,EAAA,GA7HnB,OA6HmB,CA7HX,MA6HW,CA7HJ,KA6HI,EA7HG,MA6HH,CAAA,CAAA,CAAA,GAAA;EAAU,OAAE,EA5HrD,eA4HqD,CA5HrC,KA4HqC,EA5H9B,MA4H8B,EA5HtB,UA4HsB,EA5HV,QA4HU,CAAA;EAAS,OAApE,EAAA,CAAA,SAAA,EA3HkB,UA2HlB,EAAA,GA3HiC,OA2HjC,CA3HyC,MA2HzC,CA3HgD,KA2HhD,EA3HuD,MA2HvD,CAAA,CAAA;CAAiB;;;;;;;;;;AA+LG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA9QR,oBAAA,cAAkC;iDAmE1C,eACD,2BACK,gCACQ,wCAET,iBACR,cACA,QACA,OACA,YACA,eAEC,kBAAkB,cAAc,QAAQ,OAAO,YAAY;kFA8LpD,oBAAoB,OAAO,QAAQ,YAAY,cACtD,qBAAqB,OAAO,QAAQ,YAAY"}
@@ -50,13 +50,17 @@ function createQueryFactories(queryClient) {
50
50
  * This factory function is the cornerstone of our data fetching architecture. It wraps service calls
51
51
  * with TanStack Query superpowers while maintaining type safety through Result types.
52
52
  *
53
+ * The returned query definition is **directly callable** and defaults to `ensure()` behavior,
54
+ * which is recommended for most imperative use cases like preloaders.
55
+ *
53
56
  * ## Why use defineQuery?
54
57
  *
55
- * 1. **Dual Interface**: Provides both reactive (`.options`) and imperative (`.fetch()`) APIs
56
- * 2. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically
58
+ * 1. **Callable**: Call directly like `userQuery()` for imperative data fetching
59
+ * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.fetch()`, `.ensure()`) APIs
60
+ * 3. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically
57
61
  * unwrapped by TanStack Query, giving you proper error states in your components
58
- * 3. **Type Safety**: Full TypeScript support with proper inference for data and error types
59
- * 4. **Consistency**: Every query in the app follows the same pattern, making it easy to understand
62
+ * 4. **Type Safety**: Full TypeScript support with proper inference for data and error types
63
+ * 5. **Consistency**: Every query in the app follows the same pattern, making it easy to understand
60
64
  *
61
65
  * @template TQueryFnData - The type of data returned by the query function
62
66
  * @template TError - The type of error that can be thrown
@@ -68,10 +72,11 @@ function createQueryFactories(queryClient) {
68
72
  * @param options.resultQueryFn - Function that fetches data and returns a Result type
69
73
  * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)
70
74
  *
71
- * @returns Query definition object with three methods:
72
- * - `options()`: Returns config for use with useQuery() or createQuery()
73
- * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)
74
- * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
75
+ * @returns Callable query definition with:
76
+ * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not
77
+ * - `.options`: Config for use with useQuery() or createQuery()
78
+ * - `.fetch()`: Always attempts to fetch (from cache if fresh, network if stale)
79
+ * - `.ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
75
80
  *
76
81
  * @example
77
82
  * ```typescript
@@ -87,16 +92,16 @@ function createQueryFactories(queryClient) {
87
92
  * // $query.data is User | undefined
88
93
  * // $query.error is ApiError | null
89
94
  *
90
- * // Step 2b: Use imperatively in preloaders (recommended)
95
+ * // Step 2b: Call directly in preloaders (recommended)
91
96
  * export const load = async () => {
92
- * const { data, error } = await userQuery.ensure();
97
+ * const { data, error } = await userQuery(); // Same as userQuery.ensure()
93
98
  * if (error) throw error;
94
99
  * return { user: data };
95
100
  * };
96
101
  *
97
- * // Step 2c: Use imperatively for explicit refresh
102
+ * // Step 2c: Use explicit methods when needed
98
103
  * async function refreshUser() {
99
- * const { data, error } = await userQuery.fetch();
104
+ * const { data, error } = await userQuery.fetch(); // Force fresh fetch
100
105
  * if (error) {
101
106
  * console.error('Failed to fetch user:', error);
102
107
  * }
@@ -112,29 +117,87 @@ function createQueryFactories(queryClient) {
112
117
  return resolve(result);
113
118
  }
114
119
  };
115
- return {
116
- options: () => newOptions,
117
- async fetch() {
118
- try {
119
- return Ok(await queryClient.fetchQuery({
120
- queryKey: newOptions.queryKey,
121
- queryFn: newOptions.queryFn
122
- }));
123
- } catch (error) {
124
- return Err(error);
125
- }
126
- },
127
- async ensure() {
128
- try {
129
- return Ok(await queryClient.ensureQueryData({
130
- queryKey: newOptions.queryKey,
131
- queryFn: newOptions.queryFn
132
- }));
133
- } catch (error) {
134
- return Err(error);
135
- }
120
+ /**
121
+ * Fetches data for this query using queryClient.fetchQuery().
122
+ *
123
+ * This method ALWAYS evaluates freshness and will refetch if data is stale.
124
+ * It wraps TanStack Query's fetchQuery method, which returns cached data if fresh
125
+ * or makes a network request if the data is stale or missing.
126
+ *
127
+ * **When to use fetch():**
128
+ * - When you explicitly want to check data freshness
129
+ * - For user-triggered refresh actions
130
+ * - When you need the most up-to-date data
131
+ *
132
+ * **For preloaders, use ensure() instead** - it's more efficient for initial data loading.
133
+ *
134
+ * @returns Promise that resolves with a Result containing either the data or an error
135
+ *
136
+ * @example
137
+ * // Good for user-triggered refresh
138
+ * const { data, error } = await userQuery.fetch();
139
+ * if (error) {
140
+ * console.error('Failed to load user:', error);
141
+ * }
142
+ */
143
+ async function fetch() {
144
+ try {
145
+ return Ok(await queryClient.fetchQuery({
146
+ queryKey: newOptions.queryKey,
147
+ queryFn: newOptions.queryFn
148
+ }));
149
+ } catch (error) {
150
+ return Err(error);
136
151
  }
137
- };
152
+ }
153
+ /**
154
+ * Ensures data is available for this query using queryClient.ensureQueryData().
155
+ *
156
+ * This method PRIORITIZES cached data and only calls fetchQuery internally if no cached
157
+ * data exists. It wraps TanStack Query's ensureQueryData method, which is perfect for
158
+ * guaranteeing data availability with minimal network requests.
159
+ *
160
+ * **This is the RECOMMENDED method for preloaders** because:
161
+ * - It returns cached data immediately if available
162
+ * - It updates the query client cache properly
163
+ * - It minimizes network requests during navigation
164
+ * - It ensures components have data ready when they mount
165
+ *
166
+ * **When to use ensure():**
167
+ * - Route preloaders and data loading functions
168
+ * - Initial component data requirements
169
+ * - When cached data is acceptable for immediate display
170
+ *
171
+ * This is also the default behavior when calling the query directly.
172
+ *
173
+ * @returns Promise that resolves with a Result containing either the data or an error
174
+ *
175
+ * @example
176
+ * // Perfect for preloaders
177
+ * export const load = async () => {
178
+ * const { data, error } = await userQuery.ensure();
179
+ * // Or simply: await userQuery();
180
+ * if (error) {
181
+ * throw error;
182
+ * }
183
+ * return { user: data };
184
+ * };
185
+ */
186
+ async function ensure() {
187
+ try {
188
+ return Ok(await queryClient.ensureQueryData({
189
+ queryKey: newOptions.queryKey,
190
+ queryFn: newOptions.queryFn
191
+ }));
192
+ } catch (error) {
193
+ return Err(error);
194
+ }
195
+ }
196
+ return Object.assign(ensure, {
197
+ options: newOptions,
198
+ fetch,
199
+ ensure
200
+ });
138
201
  };
139
202
  /**
140
203
  * Creates a mutation definition for operations that modify data (create, update, delete).
@@ -143,11 +206,13 @@ function createQueryFactories(queryClient) {
143
206
  * wrap service functions that perform side effects, while maintaining the same dual interface
144
207
  * pattern for maximum flexibility.
145
208
  *
209
+ * The returned mutation definition is **directly callable**, which executes the mutation
210
+ * and returns a Result. This is equivalent to calling `.execute()`.
211
+ *
146
212
  * ## Why use defineMutation?
147
213
  *
148
- * 1. **Dual Interface**: Just like queries, mutations can be used reactively or imperatively
149
- * 2. **Direct Execution**: The `.execute()` method lets you run mutations without creating hooks,
150
- * perfect for event handlers and non-component code
214
+ * 1. **Callable**: Call directly like `createUser({ name: 'John' })` for imperative execution
215
+ * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.execute()`) APIs
151
216
  * 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring
152
217
  * errors are handled consistently throughout the app
153
218
  * 4. **Cache Management**: Mutations often update the cache after success (see examples)
@@ -162,9 +227,10 @@ function createQueryFactories(queryClient) {
162
227
  * @param options.resultMutationFn - Function that performs the mutation and returns a Result type
163
228
  * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)
164
229
  *
165
- * @returns Mutation definition object with two methods:
166
- * - `options()`: Returns config for use with useMutation() or createMutation()
167
- * - `execute()`: Directly executes the mutation and returns a Result
230
+ * @returns Callable mutation definition with:
231
+ * - `(variables)` (callable): Same as `execute()` - directly executes the mutation
232
+ * - `.options`: Config for use with useMutation() or createMutation()
233
+ * - `.execute(variables)`: Directly executes the mutation and returns a Result
168
234
  *
169
235
  * @example
170
236
  * ```typescript
@@ -189,18 +255,18 @@ function createQueryFactories(queryClient) {
189
255
  * const mutation = createMutation(createRecording.options);
190
256
  * // Call with: $mutation.mutate(recordingData)
191
257
  *
192
- * // Step 2b: Use imperatively in an action
258
+ * // Step 2b: Call directly in an action (recommended)
193
259
  * async function saveRecording(data: Recording) {
194
- * const { error } = await createRecording.execute(data);
260
+ * const { error } = await createRecording(data); // Same as createRecording.execute(data)
195
261
  * if (error) {
196
- * notify.error.execute({ title: 'Failed to save', description: error.message });
262
+ * notify.error({ title: 'Failed to save', description: error.message });
197
263
  * } else {
198
- * notify.success.execute({ title: 'Recording saved!' });
264
+ * notify.success({ title: 'Recording saved!' });
199
265
  * }
200
266
  * }
201
267
  * ```
202
268
  *
203
- * @tip The imperative `.execute()` method is especially useful for:
269
+ * @tip Calling directly is especially useful for:
204
270
  * - Event handlers that need to await the result
205
271
  * - Sequential operations that depend on each other
206
272
  * - Non-component code that needs to trigger mutations
@@ -212,16 +278,46 @@ function createQueryFactories(queryClient) {
212
278
  return resolve(await options.resultMutationFn(variables));
213
279
  }
214
280
  };
215
- return {
216
- options: () => newOptions,
217
- async execute(variables) {
218
- try {
219
- return Ok(await executeMutation(queryClient, newOptions, variables));
220
- } catch (error) {
221
- return Err(error);
222
- }
281
+ /**
282
+ * Executes the mutation imperatively and returns a Result.
283
+ *
284
+ * This is the recommended way to trigger mutations from:
285
+ * - Button click handlers
286
+ * - Form submissions
287
+ * - Keyboard shortcuts
288
+ * - Any non-component code
289
+ *
290
+ * The method automatically wraps the result in a Result type, so you always
291
+ * get back `{ data, error }` for consistent error handling.
292
+ *
293
+ * This is also the default behavior when calling the mutation directly.
294
+ *
295
+ * @param variables - The variables to pass to the mutation function
296
+ * @returns Promise that resolves with a Result containing either the data or an error
297
+ *
298
+ * @example
299
+ * // In an event handler
300
+ * async function handleSubmit(formData: FormData) {
301
+ * const { data, error } = await createUser.execute(formData);
302
+ * // Or simply: await createUser(formData);
303
+ * if (error) {
304
+ * notify.error({ title: 'Failed to create user', description: error.message });
305
+ * return;
306
+ * }
307
+ * goto(`/users/${data.id}`);
308
+ * }
309
+ */
310
+ async function execute(variables) {
311
+ try {
312
+ return Ok(await runMutation(queryClient, newOptions, variables));
313
+ } catch (error) {
314
+ return Err(error);
223
315
  }
224
- };
316
+ }
317
+ return Object.assign(execute, {
318
+ options: newOptions,
319
+ execute
320
+ });
225
321
  };
226
322
  return {
227
323
  defineQuery,
@@ -231,9 +327,9 @@ function createQueryFactories(queryClient) {
231
327
  /**
232
328
  * Internal helper that executes a mutation directly using the query client's mutation cache.
233
329
  *
234
- * This is what powers the `.execute()` method on mutations. It bypasses the reactive
235
- * mutation hooks and runs the mutation imperatively, which is perfect for event handlers
236
- * and other imperative code.
330
+ * This is what powers the callable behavior and `.execute()` method on mutations.
331
+ * It bypasses the reactive mutation hooks and runs the mutation imperatively,
332
+ * which is perfect for event handlers and other imperative code.
237
333
  *
238
334
  * @internal
239
335
  * @template TData - The type of data returned by the mutation
@@ -245,7 +341,7 @@ function createQueryFactories(queryClient) {
245
341
  * @param variables - The variables to pass to the mutation function
246
342
  * @returns Promise that resolves with the mutation result
247
343
  */
248
- function executeMutation(queryClient, options, variables) {
344
+ function runMutation(queryClient, options, variables) {
249
345
  const mutation = queryClient.getMutationCache().build(queryClient, options);
250
346
  return mutation.execute(variables);
251
347
  }
@@ -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 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"}
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 * The query definition is directly callable and defaults to `ensure()` behavior,\n * which is recommended for most imperative use cases like preloaders.\n *\n * Provides both reactive and imperative interfaces for data fetching:\n * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not\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 *\n * @example\n * ```typescript\n * const userQuery = defineQuery({...});\n *\n * // Directly callable (same as .ensure())\n * const { data, error } = await userQuery();\n *\n * // Or use explicit methods\n * const { data, error } = await userQuery.ensure();\n * const { data, error } = await userQuery.fetch();\n *\n * // For reactive usage\n * const query = createQuery(userQuery.options);\n * ```\n */\nexport type DefineQueryOutput<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tTQueryKey extends QueryKey = QueryKey,\n> = (() => Promise<Result<TQueryData, TError>>) & {\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 * The mutation definition is directly callable, which executes the mutation\n * and returns a Result. This is equivalent to calling `.execute()`.\n *\n * Provides both reactive and imperative interfaces for data mutations:\n * - `(variables)` (callable): Same as `execute()` - directly executes the mutation\n * - `options`: Returns config for use with useMutation() or createMutation()\n * - `execute(variables)`: 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 *\n * @example\n * ```typescript\n * const createUser = defineMutation({...});\n *\n * // Directly callable (same as .execute())\n * const { data, error } = await createUser({ name: 'John' });\n *\n * // Or use explicit method\n * const { data, error } = await createUser.execute({ name: 'John' });\n *\n * // For reactive usage\n * const mutation = createMutation(createUser.options);\n * ```\n */\nexport type DefineMutationOutput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = ((variables: TVariables) => Promise<Result<TData, TError>>) & {\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 * The returned query definition is **directly callable** and defaults to `ensure()` behavior,\n\t * which is recommended for most imperative use cases like preloaders.\n\t *\n\t * ## Why use defineQuery?\n\t *\n\t * 1. **Callable**: Call directly like `userQuery()` for imperative data fetching\n\t * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.fetch()`, `.ensure()`) APIs\n\t * 3. **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 * 4. **Type Safety**: Full TypeScript support with proper inference for data and error types\n\t * 5. **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 Callable query definition with:\n\t * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not\n\t * - `.options`: Config for use with useQuery() or createQuery()\n\t * - `.fetch()`: Always attempts to fetch (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: Call directly in preloaders (recommended)\n\t * export const load = async () => {\n\t * const { data, error } = await userQuery(); // Same as userQuery.ensure()\n\t * if (error) throw error;\n\t * return { user: data };\n\t * };\n\t *\n\t * // Step 2c: Use explicit methods when needed\n\t * async function refreshUser() {\n\t * const { data, error } = await userQuery.fetch(); // Force fresh 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\t/**\n\t\t * Fetches data for this query using queryClient.fetchQuery().\n\t\t *\n\t\t * This method ALWAYS evaluates freshness and will refetch if data is stale.\n\t\t * It wraps TanStack Query's fetchQuery method, which returns cached data if fresh\n\t\t * or makes a network request if the data is stale or missing.\n\t\t *\n\t\t * **When to use fetch():**\n\t\t * - When you explicitly want to check data freshness\n\t\t * - For user-triggered refresh actions\n\t\t * - When you need the most up-to-date data\n\t\t *\n\t\t * **For preloaders, use ensure() instead** - it's more efficient for initial data loading.\n\t\t *\n\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t *\n\t\t * @example\n\t\t * // Good for user-triggered refresh\n\t\t * const { data, error } = await userQuery.fetch();\n\t\t * if (error) {\n\t\t * console.error('Failed to load user:', error);\n\t\t * }\n\t\t */\n\t\tasync function fetch(): Promise<Result<TQueryData, TError>> {\n\t\t\ttry {\n\t\t\t\treturn Ok(\n\t\t\t\t\tawait queryClient.fetchQuery<\n\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\tTError,\n\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t>({\n\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treturn Err(error as TError);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Ensures data is available for this query using queryClient.ensureQueryData().\n\t\t *\n\t\t * This method PRIORITIZES cached data and only calls fetchQuery internally if no cached\n\t\t * data exists. It wraps TanStack Query's ensureQueryData method, which is perfect for\n\t\t * guaranteeing data availability with minimal network requests.\n\t\t *\n\t\t * **This is the RECOMMENDED method for preloaders** because:\n\t\t * - It returns cached data immediately if available\n\t\t * - It updates the query client cache properly\n\t\t * - It minimizes network requests during navigation\n\t\t * - It ensures components have data ready when they mount\n\t\t *\n\t\t * **When to use ensure():**\n\t\t * - Route preloaders and data loading functions\n\t\t * - Initial component data requirements\n\t\t * - When cached data is acceptable for immediate display\n\t\t *\n\t\t * This is also the default behavior when calling the query directly.\n\t\t *\n\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t *\n\t\t * @example\n\t\t * // Perfect for preloaders\n\t\t * export const load = async () => {\n\t\t * const { data, error } = await userQuery.ensure();\n\t\t * // Or simply: await userQuery();\n\t\t * if (error) {\n\t\t * throw error;\n\t\t * }\n\t\t * return { user: data };\n\t\t * };\n\t\t */\n\t\tasync function ensure(): Promise<Result<TQueryData, TError>> {\n\t\t\ttry {\n\t\t\t\treturn Ok(\n\t\t\t\t\tawait queryClient.ensureQueryData<\n\t\t\t\t\t\tTQueryFnData,\n\t\t\t\t\t\tTError,\n\t\t\t\t\t\tTQueryData,\n\t\t\t\t\t\tTQueryKey\n\t\t\t\t\t>({\n\t\t\t\t\t\tqueryKey: newOptions.queryKey,\n\t\t\t\t\t\tqueryFn: newOptions.queryFn,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treturn Err(error as TError);\n\t\t\t}\n\t\t}\n\n\t\t// Create a callable function that defaults to ensure() behavior\n\t\t// and attach options, fetch, and ensure as properties\n\t\treturn Object.assign(ensure, {\n\t\t\toptions: newOptions,\n\t\t\tfetch,\n\t\t\tensure,\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 * The returned mutation definition is **directly callable**, which executes the mutation\n\t * and returns a Result. This is equivalent to calling `.execute()`.\n\t *\n\t * ## Why use defineMutation?\n\t *\n\t * 1. **Callable**: Call directly like `createUser({ name: 'John' })` for imperative execution\n\t * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.execute()`) APIs\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 Callable mutation definition with:\n\t * - `(variables)` (callable): Same as `execute()` - directly executes the mutation\n\t * - `.options`: Config for use with useMutation() or createMutation()\n\t * - `.execute(variables)`: 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: Call directly in an action (recommended)\n\t * async function saveRecording(data: Recording) {\n\t * const { error } = await createRecording(data); // Same as createRecording.execute(data)\n\t * if (error) {\n\t * notify.error({ title: 'Failed to save', description: error.message });\n\t * } else {\n\t * notify.success({ title: 'Recording saved!' });\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @tip Calling directly 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\t/**\n\t\t * Executes the mutation imperatively and returns a Result.\n\t\t *\n\t\t * This is the recommended way to trigger mutations from:\n\t\t * - Button click handlers\n\t\t * - Form submissions\n\t\t * - Keyboard shortcuts\n\t\t * - Any non-component code\n\t\t *\n\t\t * The method automatically wraps the result in a Result type, so you always\n\t\t * get back `{ data, error }` for consistent error handling.\n\t\t *\n\t\t * This is also the default behavior when calling the mutation directly.\n\t\t *\n\t\t * @param variables - The variables to pass to the mutation function\n\t\t * @returns Promise that resolves with a Result containing either the data or an error\n\t\t *\n\t\t * @example\n\t\t * // In an event handler\n\t\t * async function handleSubmit(formData: FormData) {\n\t\t * const { data, error } = await createUser.execute(formData);\n\t\t * // Or simply: await createUser(formData);\n\t\t * if (error) {\n\t\t * notify.error({ title: 'Failed to create user', description: error.message });\n\t\t * return;\n\t\t * }\n\t\t * goto(`/users/${data.id}`);\n\t\t * }\n\t\t */\n\t\tasync function execute(variables: TVariables) {\n\t\t\ttry {\n\t\t\t\treturn Ok(await runMutation(queryClient, newOptions, variables));\n\t\t\t} catch (error) {\n\t\t\t\treturn Err(error as TError);\n\t\t\t}\n\t\t}\n\n\t\t// Create a callable function that executes the mutation\n\t\t// and attach options and execute as properties\n\t\treturn Object.assign(execute, {\n\t\t\toptions: newOptions,\n\t\t\texecute,\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 callable behavior and `.execute()` method on mutations.\n * It bypasses the reactive mutation hooks and runs the mutation imperatively,\n * which is perfect for event handlers 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 runMutation<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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+LA,SAAgB,qBAAqBA,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiE9D,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;;;;;;;;;;;;;;;;;;;;;;;;EA+BD,eAAe,QAA6C;AAC3D,OAAI;AACH,WAAO,GACN,MAAM,YAAY,WAKhB;KACD,UAAU,WAAW;KACrB,SAAS,WAAW;IACpB,EAAC,CACF;GACD,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCD,eAAe,SAA8C;AAC5D,OAAI;AACH,WAAO,GACN,MAAM,YAAY,gBAKhB;KACD,UAAU,WAAW;KACrB,SAAS,WAAW;IACpB,EAAC,CACF;GACD,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;AAID,SAAO,OAAO,OAAO,QAAQ;GAC5B,SAAS;GACT;GACA;EACA,EAAC;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0ED,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BD,eAAe,QAAQA,WAAuB;AAC7C,OAAI;AACH,WAAO,GAAG,MAAM,YAAY,aAAa,YAAY,UAAU,CAAC;GAChE,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;AAID,SAAO,OAAO,OAAO,SAAS;GAC7B,SAAS;GACT;EACA,EAAC;CACF;AAED,QAAO;EACN;EACA;CACA;AACD;;;;;;;;;;;;;;;;;;AAmBD,SAAS,YACRH,aACAI,SACAD,WACC;CACD,MAAM,WAAW,YAAY,kBAAkB,CAAC,MAAM,aAAa,QAAQ;AAC3E,QAAO,SAAS,QAAQ,UAAU;AAClC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wellcrafted",
3
- "version": "0.25.1",
3
+ "version": "0.26.0",
4
4
  "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
5
  "type": "module",
6
6
  "files": [