wellcrafted 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -210,7 +210,7 @@ getUser(orderId); // type error
210
210
 
211
211
  ### Query Integration
212
212
 
213
- TanStack Query factories with a dual interface: `.options` for reactive components, callable for imperative use in event handlers.
213
+ TanStack Query factories with `.options` for reactive components and explicit imperative helpers for event handlers.
214
214
 
215
215
  ```typescript
216
216
  import { createQueryFactories } from "wellcrafted/query";
@@ -222,10 +222,10 @@ const userQuery = defineQuery({
222
222
  queryFn: () => getUser(userId), // returns Result<User, UserError>
223
223
  });
224
224
 
225
- // Reactive pass to useQuery (React) or createQuery (Svelte)
225
+ // Reactive: pass to useQuery (React) or createQuery (Svelte)
226
226
  const query = createQuery(() => userQuery.options);
227
227
 
228
- // Imperative direct execution for event handlers
228
+ // Imperative: choose the query read policy explicitly
229
229
  const { data, error } = await userQuery.fetch();
230
230
  ```
231
231
 
@@ -273,8 +273,8 @@ The same principle applies throughout: async/await instead of generators, `switc
273
273
  ### Query functions
274
274
 
275
275
  - **`createQueryFactories(client)`** — create query/mutation factories for TanStack Query
276
- - **`defineQuery(options)`** define a query with dual interface (`.options` for reactive, callable for imperative)
277
- - **`defineMutation(options)`** define a mutation with dual interface
276
+ - **`defineQuery(options)`**: define a query with `.options`, `.fetch()`, and `.ensure()`
277
+ - **`defineMutation(options)`**: define a callable mutation with `.options` for hooks
278
278
 
279
279
  ### Standard Schema
280
280
 
@@ -299,7 +299,7 @@ This installs 5 skills that teach your agent the patterns, anti-patterns, and AP
299
299
  | --- | --- |
300
300
  | `define-errors` | `defineErrors` variants, `extractErrorMessage`, `InferErrors`/`InferError` type extraction |
301
301
  | `result-types` | `Ok`, `Err`, `trySync`/`tryAsync`, the `{ data, error }` destructuring pattern |
302
- | `query-factories` | `createQueryFactories`, `defineQuery`/`defineMutation`, dual interface (reactive + imperative) |
302
+ | `query-factories` | `createQueryFactories`, `defineQuery`/`defineMutation`, reactive options, and imperative helpers |
303
303
  | `branded-types` | `Brand<T>`, brand constructor pattern, when to add runtime validation |
304
304
  | `patterns` | Architectural style guide: control flow, factory composition, service layers, error composition |
305
305
 
@@ -1,160 +1,154 @@
1
1
  import { Result } from "../result-DKwq9BCr.js";
2
2
  import "../tap-err-CFhHBPfH.js";
3
3
  import "../index-DnoV2ZDO.js";
4
- import { DefaultError, MutationFunction, MutationKey, MutationOptions, QueryClient, QueryFunction, QueryKey, QueryObserverOptions } from "@tanstack/query-core";
4
+ import { DefaultError, MutationKey, MutationObserverOptions, QueryClient, QueryFunction, QueryKey, QueryObserverOptions } from "@tanstack/query-core";
5
5
 
6
6
  //#region src/query/utils.d.ts
7
7
 
8
8
  /**
9
- * Input options for defining a query.
9
+ * Input for `queryOptions` and `defineQuery`.
10
10
  *
11
- * Extends TanStack Query's QueryObserverOptions but expects queryFn to return a Result type.
12
- * This type represents the configuration for creating a query definition with both
13
- * reactive and imperative interfaces for data fetching.
11
+ * Mirrors TanStack Query's `QueryObserverOptions` but expects `queryFn` to
12
+ * return a Wellcrafted `Result`. The Result is unwrapped into TanStack's
13
+ * throwing data/error contract by `queryOptions`.
14
14
  *
15
- * @template TQueryFnData - The type of data returned by the query function
16
- * @template TError - The type of error that can be thrown
17
- * @template TData - The type of data returned by the query (after select transform)
18
- * @template TQueryKey - The type of the query key
15
+ * @template TQueryFnData - The success type produced by `queryFn`
16
+ * @template TError - The error type carried by the Result
17
+ * @template TData - The type seen by consumers after `select`
18
+ * @template TQueryData - The type stored in the cache (usually `TQueryFnData`)
19
+ * @template TQueryKey - The literal query key tuple
19
20
  */
20
- type DefineQueryInput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = Omit<QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>, "queryFn"> & {
21
+ type QueryOptionsInput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = Omit<QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>, "queryFn"> & {
21
22
  queryKey: TQueryKey;
22
23
  queryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;
23
24
  };
24
25
  /**
25
- * Output of defineQuery function.
26
+ * Input for `mutationOptions` and `defineMutation`.
26
27
  *
27
- * The query definition is directly callable and defaults to `ensure()` behavior,
28
- * which is recommended for most imperative use cases like preloaders.
28
+ * Mirrors TanStack Query's `MutationObserverOptions` but expects `mutationFn`
29
+ * to return a Wellcrafted `Result`. The Result is unwrapped into TanStack's
30
+ * throwing data/error contract by `mutationOptions`.
29
31
  *
30
- * Provides both reactive and imperative interfaces for data fetching:
31
- * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not
32
- * - `options`: Returns config for use with useQuery() or createQuery()
33
- * - `fetch()`: Always attempts to fetch data (from cache if fresh, network if stale)
34
- * - `ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
32
+ * @template TData - The success type produced by `mutationFn`
33
+ * @template TError - The error type carried by the Result
34
+ * @template TVariables - The variables passed to `mutationFn`
35
+ * @template TContext - The context type for optimistic updates
36
+ * @template TMutationKey - The literal mutation key tuple
37
+ */
38
+ type MutationOptionsInput<TData, TError, TVariables = void, TContext = unknown, TMutationKey extends MutationKey = MutationKey> = Omit<MutationObserverOptions<TData, TError, TVariables, TContext>, "mutationFn"> & {
39
+ mutationKey: TMutationKey;
40
+ mutationFn: (variables: TVariables) => Result<TData, TError> | Promise<Result<TData, TError>>;
41
+ };
42
+ /**
43
+ * Adapter from a Result-returning query function to TanStack Query options.
35
44
  *
36
- * @template TQueryFnData - The type of data returned by the query function
37
- * @template TError - The type of error that can be thrown
38
- * @template TData - The type of data returned by the query (after select transform)
39
- * @template TQueryKey - The type of the query key
45
+ * This is the single canonical place where `Result<TQueryFnData, TError>`
46
+ * is converted into TanStack's contract: `Ok(data)` resolves with `data`,
47
+ * `Err(error)` throws `error` into the query error channel.
40
48
  *
41
- * @example
42
- * ```typescript
43
- * const userQuery = defineQuery({...});
49
+ * Use this directly with framework hooks when you do not need the
50
+ * `QueryClient`-bound imperative helpers from `defineQuery`:
44
51
  *
45
- * // Directly callable (same as .ensure())
46
- * const { data, error } = await userQuery();
52
+ * ```ts
53
+ * const query = createQuery(() => queryOptions({
54
+ * queryKey: ['user', userId],
55
+ * queryFn: () => services.getUser(userId),
56
+ * }));
57
+ * ```
47
58
  *
48
- * // Or use explicit methods
49
- * const { data, error } = await userQuery.ensure();
50
- * const { data, error } = await userQuery.fetch();
59
+ * `defineQuery` composes through this helper, so the `.options` it returns
60
+ * is the same shape `queryOptions` produces.
51
61
  *
52
- * // For reactive usage (Svelte 5 requires accessor wrapper)
53
- * const query = createQuery(() => userQuery.options); // Svelte 5
54
- * const query = useQuery(userQuery.options); // React
55
- * ```
62
+ * @param input - Result-aware query configuration
63
+ * @returns TanStack Query `QueryObserverOptions` with `queryFn` rewired to
64
+ * resolve `Ok` and throw `Err`
56
65
  */
57
- type DefineQueryOutput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = (() => Promise<Result<TQueryData, TError>>) & {
58
- options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
59
- fetch: () => Promise<Result<TQueryData, TError>>;
60
- ensure: () => Promise<Result<TQueryData, TError>>;
61
- };
66
+ declare function queryOptions<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, const TQueryKey extends QueryKey = QueryKey>(input: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>): QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
62
67
  /**
63
- * Input options for defining a mutation.
68
+ * Adapter from a Result-returning mutation function to TanStack Query options.
64
69
  *
65
- * Extends TanStack Query's MutationOptions but expects mutationFn to return a Result type.
66
- * This type represents the configuration for creating a mutation definition with both
67
- * reactive and imperative interfaces for data mutations.
70
+ * This is the single canonical place where `Result<TData, TError>` is
71
+ * converted into TanStack's contract: `Ok(data)` resolves with `data`,
72
+ * `Err(error)` throws `error` into the mutation error channel.
68
73
  *
69
- * @template TData - The type of data returned by the mutation
70
- * @template TError - The type of error that can be thrown
71
- * @template TVariables - The type of variables passed to the mutation
72
- * @template TContext - The type of context data for optimistic updates
73
- */
74
- type DefineMutationInput<TData, TError, TVariables = void, TContext = unknown, TMutationKey extends MutationKey = MutationKey> = Omit<MutationOptions<TData, TError, TVariables, TContext>, "mutationFn"> & {
75
- mutationKey: TMutationKey;
76
- mutationFn: MutationFunction<Result<TData, TError>, TVariables>;
77
- };
78
- /**
79
- * Output of defineMutation function.
74
+ * Use this directly with framework hooks when you do not need the
75
+ * `QueryClient`-bound imperative helpers from `defineMutation`:
80
76
  *
81
- * The mutation definition is directly callable, which executes the mutation
82
- * and returns a Result. This is equivalent to calling `.execute()`.
77
+ * ```ts
78
+ * const save = createMutation(() => mutationOptions({
79
+ * mutationKey: ['saveUser'],
80
+ * mutationFn: (input: SaveUserInput) => services.saveUser(input),
81
+ * }));
82
+ * ```
83
83
  *
84
- * Provides both reactive and imperative interfaces for data mutations:
85
- * - `(variables)` (callable): Same as `execute()` - directly executes the mutation
86
- * - `options`: Returns config for use with useMutation() or createMutation()
87
- * - `execute(variables)`: Directly executes the mutation and returns a Result
84
+ * `defineMutation` composes through this helper, so the `.options` it
85
+ * returns is the same shape `mutationOptions` produces.
88
86
  *
89
- * @template TData - The type of data returned by the mutation
90
- * @template TError - The type of error that can be thrown
91
- * @template TVariables - The type of variables passed to the mutation
92
- * @template TContext - The type of context data for optimistic updates
87
+ * @param input - Result-aware mutation configuration
88
+ * @returns TanStack Query `MutationObserverOptions` with `mutationFn` rewired
89
+ * to resolve `Ok` and throw `Err`
90
+ */
91
+ declare function mutationOptions<TData, TError, TVariables = void, TContext = unknown, const TMutationKey extends MutationKey = MutationKey>(input: MutationOptionsInput<TData, TError, TVariables, TContext, TMutationKey>): MutationObserverOptions<TData, TError, TVariables, TContext>;
92
+ /**
93
+ * Output of `defineQuery`.
93
94
  *
94
- * @example
95
- * ```typescript
96
- * const createUser = defineMutation({...});
95
+ * Query imperative reads require an explicit cache policy.
97
96
  *
98
- * // Directly callable (same as .execute())
99
- * const { data, error } = await createUser({ name: 'John' });
97
+ * - `options`: Options shape produced by `queryOptions`, ready for hooks.
98
+ * - `fetch()`: Always evaluates freshness; refetches if stale.
99
+ * - `ensure()`: Prefers cached data; fetches only when missing.
100
+ */
101
+ type DefineQueryOutput<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> = {
102
+ options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
103
+ fetch: () => Promise<Result<TQueryData, TError>>;
104
+ ensure: () => Promise<Result<TQueryData, TError>>;
105
+ };
106
+ /**
107
+ * Output of `defineMutation`.
100
108
  *
101
- * // Or use explicit method
102
- * const { data, error } = await createUser.execute({ name: 'John' });
109
+ * The returned function directly executes the mutation.
103
110
  *
104
- * // For reactive usage (Svelte 5 requires accessor wrapper)
105
- * const mutation = createMutation(() => createUser.options); // Svelte 5
106
- * const mutation = useMutation(createUser.options); // React
107
- * ```
111
+ * - `(variables)` (callable): Imperatively runs the mutation, returning a Result.
112
+ * - `options`: Options shape produced by `mutationOptions`, ready for hooks.
108
113
  */
109
114
  type DefineMutationOutput<TData, TError, TVariables = void, TContext = unknown> = ((variables: TVariables) => Promise<Result<TData, TError>>) & {
110
- options: MutationOptions<TData, TError, TVariables, TContext>;
111
- execute: (variables: TVariables) => Promise<Result<TData, TError>>;
115
+ options: MutationObserverOptions<TData, TError, TVariables, TContext>;
112
116
  };
113
117
  /**
114
- * Creates factory functions for defining queries and mutations bound to a specific QueryClient.
118
+ * Creates `defineQuery` and `defineMutation` bound to a specific `QueryClient`.
115
119
  *
116
- * This factory pattern allows you to create isolated query/mutation definitions that are
117
- * bound to a specific QueryClient instance, enabling:
118
- * - Multiple query clients in the same application
119
- * - Testing with isolated query clients
120
- * - Framework-agnostic query definitions
121
- * - Proper separation of concerns between query logic and client instances
120
+ * Use this when you want a reusable query/mutation definition that carries
121
+ * its own imperative query helpers (`.fetch`, `.ensure`) and callable mutation
122
+ * execution powered by a specific client. For local one-shot options that only need
123
+ * to flow into a framework hook, prefer `queryOptions` / `mutationOptions`
124
+ * directly: those are platform-agnostic and do not require a `QueryClient`.
122
125
  *
123
- * The returned functions handle Result types automatically, unwrapping them for TanStack Query
124
- * while maintaining type safety throughout your application.
126
+ * Both `defineQuery` and `defineMutation` compose through `queryOptions` and
127
+ * `mutationOptions`, so there is exactly one place that unwraps `Result`
128
+ * into TanStack's throwing contract.
125
129
  *
126
- * @param queryClient - The QueryClient instance to bind the factories to
127
- * @returns An object containing defineQuery and defineMutation functions bound to the provided client
130
+ * @param queryClient - The TanStack `QueryClient` to bind imperative helpers to
128
131
  *
129
132
  * @example
130
- * ```typescript
131
- * // Create your query client
132
- * const queryClient = new QueryClient({
133
- * defaultOptions: {
134
- * queries: { staleTime: 5 * 60 * 1000 }
135
- * }
136
- * });
137
- *
138
- * // Create the factory functions
133
+ * ```ts
134
+ * const queryClient = new QueryClient();
139
135
  * const { defineQuery, defineMutation } = createQueryFactories(queryClient);
140
136
  *
141
- * // Now use defineQuery and defineMutation as before
142
137
  * const userQuery = defineQuery({
143
138
  * queryKey: ['user', userId],
144
- * queryFn: () => services.getUser(userId)
139
+ * queryFn: () => services.getUser(userId),
145
140
  * });
146
141
  *
147
- * // Use in components (Svelte 5 requires accessor wrapper)
148
- * const query = createQuery(() => userQuery.options); // Svelte 5
149
- * const query = useQuery(userQuery.options); // React
142
+ * // Reactive
143
+ * const query = createQuery(() => userQuery.options);
150
144
  *
151
- * // Or imperatively
145
+ * // Imperative
152
146
  * const { data, error } = await userQuery.fetch();
153
147
  * ```
154
148
  */
155
149
  declare function createQueryFactories(queryClient: QueryClient): {
156
- defineQuery: <TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryData = TQueryFnData, const TQueryKey extends QueryKey = readonly unknown[]>(options: DefineQueryInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>) => DefineQueryOutput<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
157
- defineMutation: <TData, TError, TVariables = void, TContext = unknown, const TMutationKey extends MutationKey = readonly unknown[]>(options: DefineMutationInput<TData, TError, TVariables, TContext, TMutationKey>) => DefineMutationOutput<TData, TError, TVariables, TContext>;
150
+ defineQuery: <TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryData = TQueryFnData, const TQueryKey extends QueryKey = readonly unknown[]>(input: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>) => DefineQueryOutput<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
151
+ defineMutation: <TData, TError, TVariables = void, TContext = unknown, const TMutationKey extends MutationKey = readonly unknown[]>(input: MutationOptionsInput<TData, TError, TVariables, TContext, TMutationKey>) => DefineMutationOutput<TData, TError, TVariables, TContext>;
158
152
  };
159
153
  /**
160
154
  * Identity helper for declaring a TanStack Query key map while preserving
@@ -170,7 +164,7 @@ declare function createQueryFactories(queryClient: QueryClient): {
170
164
  * contextual typing into the function body. Literal positions still widen
171
165
  * without `as const` (TS does not propagate literal narrowing through
172
166
  * contextual typing). Add `as const` to the body when you need the literal:
173
- * `(id) => ['users', id] as const` `readonly ['users', string]`.
167
+ * `(id) => ['users', id] as const` -> `readonly ['users', string]`.
174
168
  *
175
169
  * Empty arrays and non-key values are rejected at the type level.
176
170
  *
@@ -186,5 +180,5 @@ declare function createQueryFactories(queryClient: QueryClient): {
186
180
  */
187
181
  declare function defineKeys<const TKeys extends Record<string, readonly [unknown, ...unknown[]] | ((...args: never[]) => readonly [unknown, ...unknown[]])>>(keys: TKeys): TKeys;
188
182
  //#endregion
189
- export { createQueryFactories, defineKeys };
183
+ export { createQueryFactories, defineKeys, mutationOptions, queryOptions };
190
184
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/query/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAUmE;;;;;;;;KAc9D,gBAO+B,CAAA,eAAA,OAAA,EAAA,SAL1B,YAK0B,EAAA,QAJ3B,YAI2B,EAAA,aAHtB,YAGsB,EAAA,kBAFjB,QAEiB,GAFN,QAEM,CAAA,GADhC,IACgC,CAAnC,oBAAmC,CAAd,YAAc,EAAA,MAAA,EAAQ,KAAR,EAAe,UAAf,EAA2B,SAA3B,CAAA,EAAA,SAAA,CAAA,GAAA;EAAM,QAAE,EAGjC,SAHiC;EAAK,OAAE,EAIzC,aAJyC,CAI3B,MAJ2B,CAIpB,YAJoB,EAIN,MAJM,CAAA,EAIG,SAJH,CAAA;CAAU;;;;;;;;;AAItC;AAAA;;;;;;;;;;;;;;;;;;;;;;;;KAoClB,iBAeU,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;EAejB,KAAA,EAAA,GAAA,GAhBS,OAgBT,CAhBiB,MAgBE,CAhBK,UAgBL,EAhBiB,MAgBjB,CAAA,CAAA;EAAA,MAAA,EAAA,GAAA,GAfT,OAeS,CAfD,MAeC,CAfM,UAeN,EAfkB,MAelB,CAAA,CAAA;CAAA;;;;;;;;;;;;;KAAnB,mBAQQ,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,EAAA,qBAHS,WAGT,GAHuB,WAGvB,CAAA,GAFT,IAES,CAFJ,eAEI,CAFY,KAEZ,EAFmB,MAEnB,EAF2B,UAE3B,EAFuC,QAEvC,CAAA,EAAA,YAAA,CAAA,GAAA;EAAgB,WAAA,EADf,YACe;EAkCxB,UAAA,EAlCQ,gBAkCY,CAlCK,MAkCL,CAlCY,KAkCZ,EAlCmB,MAkCnB,CAAA,EAlC4B,UAkC5B,CAAA;CAAA;;;;;;;;;;;;;;;;AAOmB;AA6C5C;;;;;;;;;;;;;;;KApDK,oBAmIuC,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAAA,CAAA,CAAA,SAAA,EA9H3B,UA8H2B,EAAA,GA9HZ,OA8HY,CA9HJ,MA8HI,CA9HG,KA8HH,EA9HU,MA8HV,CAAA,CAAA,CAAA,GAAA;EAAK,OAAE,EA7HzC,eA6HyC,CA7HzB,KA6HyB,EA7HlB,MA6HkB,EA7HV,UA6HU,EA7HE,QA6HF,CAAA;EAAU,OAAE,EAAA,CAAA,SAAA,EA5HzC,UA4HyC,EAAA,GA5H1B,OA4H0B,CA5HlB,MA4HkB,CA5HX,KA4HW,EA5HJ,MA4HI,CAAA,CAAA;CAAS;;;;;;;;;;;;;AA2MhD;AAiHxB;;;;;AAMqB;;;;;;;;;;;;;;;;;;;;;;;;iBAjZL,oBAAA,cAAkC;iDAmE1C,eACD,2BACK,sCACc,wCAEf,iBACR,cACA,QACA,OACA,YACA,eAEC,kBAAkB,cAAc,QAAQ,OAAO,YAAY;oGAkMlC,2CAElB,oBACR,OACA,QACA,YACA,UACA,kBAEC,qBAAqB,OAAO,QAAQ,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiHpC,+BACK,mHAKb,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/query/utils.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAUmE;;;;;;;;;KAe9D,iBAOuC,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,OAAE,EAIrD,aAJqD,CAIvC,MAJuC,CAIhC,YAJgC,EAIlB,MAJkB,CAAA,EAIT,SAJS,CAAA;CAAS;;;;;;;;AAIjD;AAAA;;;;;KAgBlB,oBAO2B,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,EAAA,qBAFV,WAEU,GAFI,WAEJ,CAAA,GAD5B,IAC4B,CAA/B,uBAA+B,CAAP,KAAO,EAAA,MAAA,EAAQ,UAAR,EAAoB,QAApB,CAAA,EAAA,YAAA,CAAA,GAAA;EAAM,WAAE,EAG1B,YAH0B;EAAU,UAAE,EAAA,CAAA,SAAA,EAKvC,UALuC,EAAA,GAM9C,MAN8C,CAMvC,KANuC,EAMhC,MANgC,CAAA,GAMtB,OANsB,CAMd,MANc,CAMP,KANO,EAMA,MANA,CAAA,CAAA;CAAQ;;;;;;;;;;;AAMvB;AA2BrC;;;;;;;;;;;;;AAQwB,iBARR,YAQQ,CAAA,eAAA,OAAA,EAAA,SANd,YAMc,EAAA,QALf,YAKe,EAAA,aAJV,YAIU,EAAA,wBAHC,QAGD,GAHY,QAGZ,CAAA,CAAA,KAAA,EADhB,iBACgB,CADE,YACF,EADgB,MAChB,EADwB,KACxB,EAD+B,UAC/B,EAD2C,SAC3C,CAAA,CAAA,EAArB,oBAAqB,CAAA,YAAA,EAAc,MAAd,EAAsB,KAAtB,EAA6B,UAA7B,EAAyC,SAAzC,CAAA;;;;;;AAAD;AAqCvB;;;;;;;;;;;;;;;AAc0B;AAMzB;;AAaS,iBAjCM,eAiCN,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,EAAA,2BA5BkB,WA4BlB,GA5BgC,WA4BhC,CAAA,CAAA,KAAA,EA1BF,oBA0BE,CAzBR,KAyBQ,EAxBR,MAwBQ,EAvBR,UAuBQ,EAtBR,QAsBQ,EArBR,YAqBQ,CAAA,CAAA,EAnBP,uBAmBO,CAnBiB,KAmBjB,EAnBwB,MAmBxB,EAnBgC,UAmBhC,EAnB4C,QAmB5C,CAAA;;;;;;;;;;KAFL,iBAOK,CAAA,eAAA,OAAA,EAAA,SALA,YAKA,EAAA,QAJD,YAIC,EAAA,aAHI,YAGJ,EAAA,kBAFS,QAET,GAFoB,QAEpB,CAAA,GAAA;EAAoB,OAOD,EAPnB,oBAOmB,CAN3B,YAM2B,EAL3B,MAK2B,EAJ3B,KAI2B,EAH3B,UAG2B,EAF3B,SAE2B,CAAA;EAAU,KAAE,EAAA,GAAA,GAA3B,OAA2B,CAAnB,MAAmB,CAAZ,UAAY,EAAA,MAAA,CAAA,CAAA;EAAM,MAAzB,EAAA,GAAA,GACP,OADO,CACC,MADD,CACQ,UADR,EACoB,MADpB,CAAA,CAAA;CAAM;;;;;AACN;AAAA;;;KAWjB,oBAK0C,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,CAAA,GAAA,CAAA,CAAA,SAAA,EAA9B,UAA8B,EAAA,GAAf,OAAe,CAAP,MAAO,CAAA,KAAA,EAAO,MAAP,CAAA,CAAA,CAAA,GAAA;EAAK,OAAE,EAC5C,uBAD4C,CACpB,KADoB,EACb,MADa,EACL,UADK,EACO,QADP,CAAA;CAAM;;;;;;;AAC3B;AAmCjC;;;;;;;;;;;;;;;;;;;;;;;;;AA8DS,iBA9DO,oBAAA,CA8DP,WAAA,EA9DyC,WA8DzC,CAAA,EAAA;EAAoB,WAOJ,EAAA,CAAA,eAAA,OAAA,EAAA,SAlEjB,KAkEiB,EAAA,QAjElB,YAiEkB,EAAA,aAhEb,YAgEa,EAAA,wBA/DC,QA+DD,GAAA,SAAA,OAAA,EAAA,CAAA,CAAA,KAAA,EA7DhB,iBA6DgB,CA5DtB,YA4DsB,EA3DtB,MA2DsB,EA1DtB,KA0DsB,EAzDtB,UAyDsB,EAxDtB,SAwDsB,CAAA,EAAA,GAtDrB,iBAsDqB,CAtDH,YAsDG,EAtDW,MAsDX,EAtDmB,KAsDnB,EAtD0B,UAsD1B,EAtDsC,SAsDtC,CAAA;EAAK,cAAE,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,IAAA,EAAA,WAAA,OAAA,EAAA,2BATH,WASG,GAAA,SAAA,OAAA,EAAA,CAAA,CAAA,KAAA,EAPvB,oBAOuB,CAN7B,KAM6B,EAL7B,MAK6B,EAJ7B,UAI6B,EAH7B,QAG6B,EAF7B,YAE6B,CAAA,EAAA,GAA5B,oBAA4B,CAAP,KAAO,EAAA,MAAA,EAAQ,UAAR,EAAoB,QAApB,CAAA;CAAM;;;AAAd;AAkExB;;;;;AAMqB;;;;;;;;;;;;;;;;;;;;iBANL,+BACK,mHAKb,QAAQ"}
@@ -4,322 +4,130 @@ import "../result-C9V2Knvt.js";
4
4
 
5
5
  //#region src/query/utils.ts
6
6
  /**
7
- * Creates factory functions for defining queries and mutations bound to a specific QueryClient.
7
+ * Adapter from a Result-returning query function to TanStack Query options.
8
8
  *
9
- * This factory pattern allows you to create isolated query/mutation definitions that are
10
- * bound to a specific QueryClient instance, enabling:
11
- * - Multiple query clients in the same application
12
- * - Testing with isolated query clients
13
- * - Framework-agnostic query definitions
14
- * - Proper separation of concerns between query logic and client instances
9
+ * This is the single canonical place where `Result<TQueryFnData, TError>`
10
+ * is converted into TanStack's contract: `Ok(data)` resolves with `data`,
11
+ * `Err(error)` throws `error` into the query error channel.
15
12
  *
16
- * The returned functions handle Result types automatically, unwrapping them for TanStack Query
17
- * while maintaining type safety throughout your application.
13
+ * Use this directly with framework hooks when you do not need the
14
+ * `QueryClient`-bound imperative helpers from `defineQuery`:
18
15
  *
19
- * @param queryClient - The QueryClient instance to bind the factories to
20
- * @returns An object containing defineQuery and defineMutation functions bound to the provided client
16
+ * ```ts
17
+ * const query = createQuery(() => queryOptions({
18
+ * queryKey: ['user', userId],
19
+ * queryFn: () => services.getUser(userId),
20
+ * }));
21
+ * ```
21
22
  *
22
- * @example
23
- * ```typescript
24
- * // Create your query client
25
- * const queryClient = new QueryClient({
26
- * defaultOptions: {
27
- * queries: { staleTime: 5 * 60 * 1000 }
28
- * }
29
- * });
23
+ * `defineQuery` composes through this helper, so the `.options` it returns
24
+ * is the same shape `queryOptions` produces.
25
+ *
26
+ * @param input - Result-aware query configuration
27
+ * @returns TanStack Query `QueryObserverOptions` with `queryFn` rewired to
28
+ * resolve `Ok` and throw `Err`
29
+ */
30
+ function queryOptions(input) {
31
+ return {
32
+ ...input,
33
+ queryFn: async (context) => resolve(await input.queryFn(context))
34
+ };
35
+ }
36
+ /**
37
+ * Adapter from a Result-returning mutation function to TanStack Query options.
38
+ *
39
+ * This is the single canonical place where `Result<TData, TError>` is
40
+ * converted into TanStack's contract: `Ok(data)` resolves with `data`,
41
+ * `Err(error)` throws `error` into the mutation error channel.
42
+ *
43
+ * Use this directly with framework hooks when you do not need the
44
+ * `QueryClient`-bound imperative helpers from `defineMutation`:
45
+ *
46
+ * ```ts
47
+ * const save = createMutation(() => mutationOptions({
48
+ * mutationKey: ['saveUser'],
49
+ * mutationFn: (input: SaveUserInput) => services.saveUser(input),
50
+ * }));
51
+ * ```
52
+ *
53
+ * `defineMutation` composes through this helper, so the `.options` it
54
+ * returns is the same shape `mutationOptions` produces.
30
55
  *
31
- * // Create the factory functions
56
+ * @param input - Result-aware mutation configuration
57
+ * @returns TanStack Query `MutationObserverOptions` with `mutationFn` rewired
58
+ * to resolve `Ok` and throw `Err`
59
+ */
60
+ function mutationOptions(input) {
61
+ return {
62
+ ...input,
63
+ mutationFn: async (variables) => resolve(await input.mutationFn(variables))
64
+ };
65
+ }
66
+ /**
67
+ * Creates `defineQuery` and `defineMutation` bound to a specific `QueryClient`.
68
+ *
69
+ * Use this when you want a reusable query/mutation definition that carries
70
+ * its own imperative query helpers (`.fetch`, `.ensure`) and callable mutation
71
+ * execution powered by a specific client. For local one-shot options that only need
72
+ * to flow into a framework hook, prefer `queryOptions` / `mutationOptions`
73
+ * directly: those are platform-agnostic and do not require a `QueryClient`.
74
+ *
75
+ * Both `defineQuery` and `defineMutation` compose through `queryOptions` and
76
+ * `mutationOptions`, so there is exactly one place that unwraps `Result`
77
+ * into TanStack's throwing contract.
78
+ *
79
+ * @param queryClient - The TanStack `QueryClient` to bind imperative helpers to
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const queryClient = new QueryClient();
32
84
  * const { defineQuery, defineMutation } = createQueryFactories(queryClient);
33
85
  *
34
- * // Now use defineQuery and defineMutation as before
35
86
  * const userQuery = defineQuery({
36
87
  * queryKey: ['user', userId],
37
- * queryFn: () => services.getUser(userId)
88
+ * queryFn: () => services.getUser(userId),
38
89
  * });
39
90
  *
40
- * // Use in components (Svelte 5 requires accessor wrapper)
41
- * const query = createQuery(() => userQuery.options); // Svelte 5
42
- * const query = useQuery(userQuery.options); // React
91
+ * // Reactive
92
+ * const query = createQuery(() => userQuery.options);
43
93
  *
44
- * // Or imperatively
94
+ * // Imperative
45
95
  * const { data, error } = await userQuery.fetch();
46
96
  * ```
47
97
  */
48
98
  function createQueryFactories(queryClient) {
49
- /**
50
- * Creates a query definition that bridges the gap between pure service functions and reactive UI components.
51
- *
52
- * This factory function is the cornerstone of our data fetching architecture. It wraps service calls
53
- * with TanStack Query superpowers while maintaining type safety through Result types.
54
- *
55
- * The returned query definition is **directly callable** and defaults to `ensure()` behavior,
56
- * which is recommended for most imperative use cases like preloaders.
57
- *
58
- * ## Why use defineQuery?
59
- *
60
- * 1. **Callable**: Call directly like `userQuery()` for imperative data fetching
61
- * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.fetch()`, `.ensure()`) APIs
62
- * 3. **Automatic Error Handling**: Service functions return `Result<T, E>` types which are automatically
63
- * unwrapped by TanStack Query, giving you proper error states in your components
64
- * 4. **Type Safety**: Full TypeScript support with proper inference for data and error types
65
- * 5. **Consistency**: Every query in the app follows the same pattern, making it easy to understand
66
- *
67
- * @template TQueryFnData - The type of data returned by the query function
68
- * @template TError - The type of error that can be thrown
69
- * @template TData - The type of data returned by the query (after select transform)
70
- * @template TQueryKey - The type of the query key
71
- *
72
- * @param options - Query configuration object
73
- * @param options.queryKey - Unique key for this query (used for caching and refetching)
74
- * @param options.queryFn - Function that fetches data and returns a Result type
75
- * @param options.* - Any other TanStack Query options (staleTime, refetchInterval, etc.)
76
- *
77
- * @returns Callable query definition with:
78
- * - `()` (callable): Same as `ensure()` - returns cached data if available, fetches if not
79
- * - `.options`: Config for use with useQuery() or createQuery()
80
- * - `.fetch()`: Always attempts to fetch (from cache if fresh, network if stale)
81
- * - `.ensure()`: Guarantees data availability, preferring cached data (recommended for preloaders)
82
- *
83
- * @example
84
- * ```typescript
85
- * // Step 1: Define your query in the query layer
86
- * const userQuery = defineQuery({
87
- * queryKey: ['users', userId],
88
- * queryFn: () => services.getUser(userId), // Returns Result<User, ApiError>
89
- * staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
90
- * });
91
- *
92
- * // Step 2a: Use reactively in a Svelte 5 component (accessor wrapper required)
93
- * const query = createQuery(() => userQuery.options);
94
- * // query.data is User | undefined
95
- * // query.error is ApiError | null
96
- *
97
- * // Step 2b: Call directly in preloaders (recommended)
98
- * export const load = async () => {
99
- * const { data, error } = await userQuery(); // Same as userQuery.ensure()
100
- * if (error) throw error;
101
- * return { user: data };
102
- * };
103
- *
104
- * // Step 2c: Use explicit methods when needed
105
- * async function refreshUser() {
106
- * const { data, error } = await userQuery.fetch(); // Force fresh fetch
107
- * if (error) {
108
- * console.error('Failed to fetch user:', error);
109
- * }
110
- * }
111
- * ```
112
- */
113
- const defineQuery = (options) => {
114
- const newOptions = {
115
- ...options,
116
- queryFn: async (context) => {
117
- let result = options.queryFn(context);
118
- if (result instanceof Promise) result = await result;
119
- return resolve(result);
120
- }
121
- };
122
- /**
123
- * Fetches data for this query using queryClient.fetchQuery().
124
- *
125
- * This method ALWAYS evaluates freshness and will refetch if data is stale.
126
- * It wraps TanStack Query's fetchQuery method, which returns cached data if fresh
127
- * or makes a network request if the data is stale or missing.
128
- *
129
- * **When to use fetch():**
130
- * - When you explicitly want to check data freshness
131
- * - For user-triggered refresh actions
132
- * - When you need the most up-to-date data
133
- *
134
- * **For preloaders, use ensure() instead** - it's more efficient for initial data loading.
135
- *
136
- * @returns Promise that resolves with a Result containing either the data or an error
137
- *
138
- * @example
139
- * // Good for user-triggered refresh
140
- * const { data, error } = await userQuery.fetch();
141
- * if (error) {
142
- * console.error('Failed to load user:', error);
143
- * }
144
- */
99
+ const defineQuery = (input) => {
100
+ const options = queryOptions(input);
145
101
  async function fetch() {
146
102
  try {
147
- return Ok(await queryClient.fetchQuery({
148
- queryKey: newOptions.queryKey,
149
- queryFn: newOptions.queryFn
150
- }));
103
+ return Ok(await queryClient.fetchQuery(options));
151
104
  } catch (error) {
152
105
  return Err(error);
153
106
  }
154
107
  }
155
- /**
156
- * Ensures data is available for this query using queryClient.ensureQueryData().
157
- *
158
- * This method PRIORITIZES cached data and only calls fetchQuery internally if no cached
159
- * data exists. It wraps TanStack Query's ensureQueryData method, which is perfect for
160
- * guaranteeing data availability with minimal network requests.
161
- *
162
- * **This is the RECOMMENDED method for preloaders** because:
163
- * - It returns cached data immediately if available
164
- * - It updates the query client cache properly
165
- * - It minimizes network requests during navigation
166
- * - It ensures components have data ready when they mount
167
- *
168
- * **When to use ensure():**
169
- * - Route preloaders and data loading functions
170
- * - Initial component data requirements
171
- * - When cached data is acceptable for immediate display
172
- *
173
- * This is also the default behavior when calling the query directly.
174
- *
175
- * @returns Promise that resolves with a Result containing either the data or an error
176
- *
177
- * @example
178
- * // Perfect for preloaders
179
- * export const load = async () => {
180
- * const { data, error } = await userQuery.ensure();
181
- * // Or simply: await userQuery();
182
- * if (error) {
183
- * throw error;
184
- * }
185
- * return { user: data };
186
- * };
187
- */
188
108
  async function ensure() {
189
109
  try {
190
- return Ok(await queryClient.ensureQueryData({
191
- queryKey: newOptions.queryKey,
192
- queryFn: newOptions.queryFn
193
- }));
110
+ return Ok(await queryClient.ensureQueryData(options));
194
111
  } catch (error) {
195
112
  return Err(error);
196
113
  }
197
114
  }
198
- return Object.assign(ensure, {
199
- options: newOptions,
115
+ return {
116
+ options,
200
117
  fetch,
201
118
  ensure
202
- });
203
- };
204
- /**
205
- * Creates a mutation definition for operations that modify data (create, update, delete).
206
- *
207
- * This factory function is the mutation counterpart to defineQuery. It provides a clean way to
208
- * wrap service functions that perform side effects, while maintaining the same dual interface
209
- * pattern for maximum flexibility.
210
- *
211
- * The returned mutation definition is **directly callable**, which executes the mutation
212
- * and returns a Result. This is equivalent to calling `.execute()`.
213
- *
214
- * ## Why use defineMutation?
215
- *
216
- * 1. **Callable**: Call directly like `createUser({ name: 'John' })` for imperative execution
217
- * 2. **Dual Interface**: Also provides reactive (`.options`) and explicit imperative (`.execute()`) APIs
218
- * 3. **Consistent Error Handling**: Service functions return `Result<T, E>` types, ensuring
219
- * errors are handled consistently throughout the app
220
- * 4. **Cache Management**: Mutations often update the cache after success (see examples)
221
- *
222
- * @template TData - The type of data returned by the mutation
223
- * @template TError - The type of error that can be thrown
224
- * @template TVariables - The type of variables passed to the mutation
225
- * @template TContext - The type of context data for optimistic updates
226
- *
227
- * @param options - Mutation configuration object
228
- * @param options.mutationKey - Unique key for this mutation (used for tracking in-flight state)
229
- * @param options.mutationFn - Function that performs the mutation and returns a Result type
230
- * @param options.* - Any other TanStack Mutation options (onSuccess, onError, etc.)
231
- *
232
- * @returns Callable mutation definition with:
233
- * - `(variables)` (callable): Same as `execute()` - directly executes the mutation
234
- * - `.options`: Config for use with useMutation() or createMutation()
235
- * - `.execute(variables)`: Directly executes the mutation and returns a Result
236
- *
237
- * @example
238
- * ```typescript
239
- * // Step 1: Define your mutation with cache updates
240
- * const createRecording = defineMutation({
241
- * mutationKey: ['recordings', 'create'],
242
- * mutationFn: async (recording: Recording) => {
243
- * // Call the service
244
- * const result = await services.db.createRecording(recording);
245
- * if (result.error) return Err(result.error);
246
- *
247
- * // Update cache on success
248
- * queryClient.setQueryData(['recordings'], (old) =>
249
- * [...(old || []), recording]
250
- * );
251
- *
252
- * return Ok(result.data);
253
- * }
254
- * });
255
- *
256
- * // Step 2a: Use reactively in a Svelte 5 component (accessor wrapper required)
257
- * const mutation = createMutation(() => createRecording.options);
258
- * // Call with: mutation.mutate(recordingData)
259
- *
260
- * // Step 2b: Call directly in an action (recommended)
261
- * async function saveRecording(data: Recording) {
262
- * const { error } = await createRecording(data); // Same as createRecording.execute(data)
263
- * if (error) {
264
- * notify.error({ title: 'Failed to save', description: error.message });
265
- * } else {
266
- * notify.success({ title: 'Recording saved!' });
267
- * }
268
- * }
269
- * ```
270
- *
271
- * @tip Calling directly is especially useful for:
272
- * - Event handlers that need to await the result
273
- * - Sequential operations that depend on each other
274
- * - Non-component code that needs to trigger mutations
275
- */
276
- const defineMutation = (options) => {
277
- const newOptions = {
278
- ...options,
279
- mutationFn: async (variables) => {
280
- return resolve(await options.mutationFn(variables));
281
- }
282
119
  };
283
- /**
284
- * Executes the mutation imperatively and returns a Result.
285
- *
286
- * This is the recommended way to trigger mutations from:
287
- * - Button click handlers
288
- * - Form submissions
289
- * - Keyboard shortcuts
290
- * - Any non-component code
291
- *
292
- * The method automatically wraps the result in a Result type, so you always
293
- * get back `{ data, error }` for consistent error handling.
294
- *
295
- * This is also the default behavior when calling the mutation directly.
296
- *
297
- * @param variables - The variables to pass to the mutation function
298
- * @returns Promise that resolves with a Result containing either the data or an error
299
- *
300
- * @example
301
- * // In an event handler
302
- * async function handleSubmit(formData: FormData) {
303
- * const { data, error } = await createUser.execute(formData);
304
- * // Or simply: await createUser(formData);
305
- * if (error) {
306
- * notify.error({ title: 'Failed to create user', description: error.message });
307
- * return;
308
- * }
309
- * goto(`/users/${data.id}`);
310
- * }
311
- */
312
- async function execute(variables) {
120
+ };
121
+ const defineMutation = (input) => {
122
+ const options = mutationOptions(input);
123
+ async function run(variables) {
313
124
  try {
314
- return Ok(await runMutation(queryClient, newOptions, variables));
125
+ return Ok(await runMutation(queryClient, options, variables));
315
126
  } catch (error) {
316
127
  return Err(error);
317
128
  }
318
129
  }
319
- return Object.assign(execute, {
320
- options: newOptions,
321
- execute
322
- });
130
+ return Object.assign(run, { options });
323
131
  };
324
132
  return {
325
133
  defineQuery,
@@ -327,21 +135,11 @@ function createQueryFactories(queryClient) {
327
135
  };
328
136
  }
329
137
  /**
330
- * Internal helper that executes a mutation directly using the query client's mutation cache.
331
- *
332
- * This is what powers the callable behavior and `.execute()` method on mutations.
333
- * It bypasses the reactive mutation hooks and runs the mutation imperatively,
334
- * which is perfect for event handlers and other imperative code.
138
+ * Internal helper that executes a mutation directly using the query client's
139
+ * mutation cache. Powers the callable behavior on mutations returned from
140
+ * `defineMutation`.
335
141
  *
336
142
  * @internal
337
- * @template TData - The type of data returned by the mutation
338
- * @template TError - The type of error that can be thrown
339
- * @template TVariables - The type of variables passed to the mutation
340
- * @template TContext - The type of context data
341
- * @param queryClient - The query client instance to use
342
- * @param options - The mutation options including mutationFn and mutationKey
343
- * @param variables - The variables to pass to the mutation function
344
- * @returns Promise that resolves with the mutation result
345
143
  */
346
144
  function runMutation(queryClient, options, variables) {
347
145
  const mutation = queryClient.getMutationCache().build(queryClient, options);
@@ -361,7 +159,7 @@ function runMutation(queryClient, options, variables) {
361
159
  * contextual typing into the function body. Literal positions still widen
362
160
  * without `as const` (TS does not propagate literal narrowing through
363
161
  * contextual typing). Add `as const` to the body when you need the literal:
364
- * `(id) => ['users', id] as const` `readonly ['users', string]`.
162
+ * `(id) => ['users', id] as const` -> `readonly ['users', string]`.
365
163
  *
366
164
  * Empty arrays and non-key values are rejected at the type level.
367
165
  *
@@ -380,5 +178,5 @@ function defineKeys(keys) {
380
178
  }
381
179
 
382
180
  //#endregion
383
- export { createQueryFactories, defineKeys };
181
+ export { createQueryFactories, defineKeys, mutationOptions, queryOptions };
384
182
  //# sourceMappingURL=index.js.map
@@ -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<\n\t\t\tTData,\n\t\t\tTError,\n\t\t\tTVariables,\n\t\t\tTContext,\n\t\t\tTMutationKey\n\t\t>","variables: TVariables","options: MutationOptions<TData, TError, TVariables, TContext>","keys: TKeys"],"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 expects queryFn to return a Result type.\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 */\ntype 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\tqueryFn: 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 (Svelte 5 requires accessor wrapper)\n * const query = createQuery(() => userQuery.options); // Svelte 5\n * const query = useQuery(userQuery.options); // React\n * ```\n */\ntype 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 expects mutationFn to return a Result type.\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 */\ntype DefineMutationInput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n\tTMutationKey extends MutationKey = MutationKey,\n> = Omit<MutationOptions<TData, TError, TVariables, TContext>, \"mutationFn\"> & {\n\tmutationKey: TMutationKey;\n\tmutationFn: 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 (Svelte 5 requires accessor wrapper)\n * const mutation = createMutation(() => createUser.options); // Svelte 5\n * const mutation = useMutation(createUser.options); // React\n * ```\n */\ntype 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 * queryFn: () => services.getUser(userId)\n * });\n *\n * // Use in components (Svelte 5 requires accessor wrapper)\n * const query = createQuery(() => userQuery.options); // Svelte 5\n * const query = useQuery(userQuery.options); // React\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.queryFn - 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 * queryFn: () => 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 5 component (accessor wrapper required)\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\tconst TQueryKey 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.queryFn(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.mutationFn - 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 * mutationFn: 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 Svelte 5 component (accessor wrapper required)\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 = <\n\t\tTData,\n\t\tTError,\n\t\tTVariables = void,\n\t\tTContext = unknown,\n\t\tconst TMutationKey extends MutationKey = MutationKey,\n\t>(\n\t\toptions: DefineMutationInput<\n\t\t\tTData,\n\t\t\tTError,\n\t\t\tTVariables,\n\t\t\tTContext,\n\t\t\tTMutationKey\n\t\t>,\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.mutationFn(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\n/**\n * Identity helper for declaring a TanStack Query key map while preserving\n * tuple types.\n *\n * - **Static entries** like `['users', 'active']` are narrowed to readonly\n * tuples with full literal precision (e.g. `readonly ['users', 'active']`)\n * via the `const` type parameter modifier. No per-line `as const` needed.\n *\n * - **Factory entries** like `(id: string) => ['users', id]` are narrowed to\n * tuple SHAPE (e.g. `[string, string]` with correct arity), not widened to\n * `string[]`. This happens because the strict tuple constraint provides\n * contextual typing into the function body. Literal positions still widen\n * without `as const` (TS does not propagate literal narrowing through\n * contextual typing). Add `as const` to the body when you need the literal:\n * `(id) => ['users', id] as const` → `readonly ['users', string]`.\n *\n * Empty arrays and non-key values are rejected at the type level.\n *\n * @example\n * ```ts\n * const userKeys = defineKeys({\n * all: ['users'], // readonly ['users']\n * active: ['users', 'active'], // readonly ['users', 'active']\n * detail: (id: string) => ['users', id], // [string, string] (tuple shape kept)\n * page: (n: number) => ['users', n] as const, // readonly ['users', number]\n * });\n * ```\n */\nexport function defineKeys<\n\tconst TKeys extends Record<\n\t\tstring,\n\t\t| readonly [unknown, ...unknown[]]\n\t\t| ((...args: never[]) => readonly [unknown, ...unknown[]])\n\t>,\n>(keys: TKeys): TKeys {\n\treturn keys;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmMA,SAAgB,qBAAqBA,aAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiE9D,MAAM,cAAc,CAOnBC,YAO2E;EAC3E,MAAM,aAAa;GAClB,GAAG;GACH,SAAS,OAAO,YAAY;IAC3B,IAAI,SAAS,QAAQ,QAAQ,QAAQ;AACrC,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,CAOtBC,YAO+D;EAC/D,MAAM,aAAa;GAClB,GAAG;GACH,YAAY,OAAOC,cAA0B;AAC5C,WAAO,QAAQ,MAAM,QAAQ,WAAW,UAAU,CAAC;GACnD;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,WAMdE,MAAoB;AACrB,QAAO;AACP"}
1
+ {"version":3,"file":"index.js","names":["input: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>","input: MutationOptionsInput<\n\t\tTData,\n\t\tTError,\n\t\tTVariables,\n\t\tTContext,\n\t\tTMutationKey\n\t>","variables: TVariables","queryClient: QueryClient","input: QueryOptionsInput<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryData,\n\t\t\tTQueryKey\n\t\t>","input: MutationOptionsInput<\n\t\t\tTData,\n\t\t\tTError,\n\t\t\tTVariables,\n\t\t\tTContext,\n\t\t\tTMutationKey\n\t\t>","options: MutationOptions<TData, TError, TVariables, TContext>","keys: TKeys"],"sources":["../../src/query/utils.ts"],"sourcesContent":["import type {\n\tDefaultError,\n\tMutationKey,\n\tMutationObserverOptions,\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 for `queryOptions` and `defineQuery`.\n *\n * Mirrors TanStack Query's `QueryObserverOptions` but expects `queryFn` to\n * return a Wellcrafted `Result`. The Result is unwrapped into TanStack's\n * throwing data/error contract by `queryOptions`.\n *\n * @template TQueryFnData - The success type produced by `queryFn`\n * @template TError - The error type carried by the Result\n * @template TData - The type seen by consumers after `select`\n * @template TQueryData - The type stored in the cache (usually `TQueryFnData`)\n * @template TQueryKey - The literal query key tuple\n */\ntype QueryOptionsInput<\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\tqueryFn: QueryFunction<Result<TQueryFnData, TError>, TQueryKey>;\n};\n\n/**\n * Input for `mutationOptions` and `defineMutation`.\n *\n * Mirrors TanStack Query's `MutationObserverOptions` but expects `mutationFn`\n * to return a Wellcrafted `Result`. The Result is unwrapped into TanStack's\n * throwing data/error contract by `mutationOptions`.\n *\n * @template TData - The success type produced by `mutationFn`\n * @template TError - The error type carried by the Result\n * @template TVariables - The variables passed to `mutationFn`\n * @template TContext - The context type for optimistic updates\n * @template TMutationKey - The literal mutation key tuple\n */\ntype MutationOptionsInput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n\tTMutationKey extends MutationKey = MutationKey,\n> = Omit<\n\tMutationObserverOptions<TData, TError, TVariables, TContext>,\n\t\"mutationFn\"\n> & {\n\tmutationKey: TMutationKey;\n\tmutationFn: (\n\t\tvariables: TVariables,\n\t) => Result<TData, TError> | Promise<Result<TData, TError>>;\n};\n\n/**\n * Adapter from a Result-returning query function to TanStack Query options.\n *\n * This is the single canonical place where `Result<TQueryFnData, TError>`\n * is converted into TanStack's contract: `Ok(data)` resolves with `data`,\n * `Err(error)` throws `error` into the query error channel.\n *\n * Use this directly with framework hooks when you do not need the\n * `QueryClient`-bound imperative helpers from `defineQuery`:\n *\n * ```ts\n * const query = createQuery(() => queryOptions({\n * queryKey: ['user', userId],\n * queryFn: () => services.getUser(userId),\n * }));\n * ```\n *\n * `defineQuery` composes through this helper, so the `.options` it returns\n * is the same shape `queryOptions` produces.\n *\n * @param input - Result-aware query configuration\n * @returns TanStack Query `QueryObserverOptions` with `queryFn` rewired to\n * resolve `Ok` and throw `Err`\n */\nexport function queryOptions<\n\tTQueryFnData = unknown,\n\tTError = DefaultError,\n\tTData = TQueryFnData,\n\tTQueryData = TQueryFnData,\n\tconst TQueryKey extends QueryKey = QueryKey,\n>(\n\tinput: QueryOptionsInput<TQueryFnData, TError, TData, TQueryData, TQueryKey>,\n): QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> {\n\treturn {\n\t\t...input,\n\t\tqueryFn: async (context) => resolve(await input.queryFn(context)),\n\t} satisfies QueryObserverOptions<\n\t\tTQueryFnData,\n\t\tTError,\n\t\tTData,\n\t\tTQueryData,\n\t\tTQueryKey\n\t>;\n}\n\n/**\n * Adapter from a Result-returning mutation function to TanStack Query options.\n *\n * This is the single canonical place where `Result<TData, TError>` is\n * converted into TanStack's contract: `Ok(data)` resolves with `data`,\n * `Err(error)` throws `error` into the mutation error channel.\n *\n * Use this directly with framework hooks when you do not need the\n * `QueryClient`-bound imperative helpers from `defineMutation`:\n *\n * ```ts\n * const save = createMutation(() => mutationOptions({\n * mutationKey: ['saveUser'],\n * mutationFn: (input: SaveUserInput) => services.saveUser(input),\n * }));\n * ```\n *\n * `defineMutation` composes through this helper, so the `.options` it\n * returns is the same shape `mutationOptions` produces.\n *\n * @param input - Result-aware mutation configuration\n * @returns TanStack Query `MutationObserverOptions` with `mutationFn` rewired\n * to resolve `Ok` and throw `Err`\n */\nexport function mutationOptions<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n\tconst TMutationKey extends MutationKey = MutationKey,\n>(\n\tinput: MutationOptionsInput<\n\t\tTData,\n\t\tTError,\n\t\tTVariables,\n\t\tTContext,\n\t\tTMutationKey\n\t>,\n): MutationObserverOptions<TData, TError, TVariables, TContext> {\n\treturn {\n\t\t...input,\n\t\tmutationFn: async (variables: TVariables) =>\n\t\t\tresolve(await input.mutationFn(variables)),\n\t} satisfies MutationObserverOptions<TData, TError, TVariables, TContext>;\n}\n\n/**\n * Output of `defineQuery`.\n *\n * Query imperative reads require an explicit cache policy.\n *\n * - `options`: Options shape produced by `queryOptions`, ready for hooks.\n * - `fetch()`: Always evaluates freshness; refetches if stale.\n * - `ensure()`: Prefers cached data; fetches only when missing.\n */\ntype 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 * Output of `defineMutation`.\n *\n * The returned function directly executes the mutation.\n *\n * - `(variables)` (callable): Imperatively runs the mutation, returning a Result.\n * - `options`: Options shape produced by `mutationOptions`, ready for hooks.\n */\ntype DefineMutationOutput<\n\tTData,\n\tTError,\n\tTVariables = void,\n\tTContext = unknown,\n> = ((variables: TVariables) => Promise<Result<TData, TError>>) & {\n\toptions: MutationObserverOptions<TData, TError, TVariables, TContext>;\n};\n\n/**\n * Creates `defineQuery` and `defineMutation` bound to a specific `QueryClient`.\n *\n * Use this when you want a reusable query/mutation definition that carries\n * its own imperative query helpers (`.fetch`, `.ensure`) and callable mutation\n * execution powered by a specific client. For local one-shot options that only need\n * to flow into a framework hook, prefer `queryOptions` / `mutationOptions`\n * directly: those are platform-agnostic and do not require a `QueryClient`.\n *\n * Both `defineQuery` and `defineMutation` compose through `queryOptions` and\n * `mutationOptions`, so there is exactly one place that unwraps `Result`\n * into TanStack's throwing contract.\n *\n * @param queryClient - The TanStack `QueryClient` to bind imperative helpers to\n *\n * @example\n * ```ts\n * const queryClient = new QueryClient();\n * const { defineQuery, defineMutation } = createQueryFactories(queryClient);\n *\n * const userQuery = defineQuery({\n * queryKey: ['user', userId],\n * queryFn: () => services.getUser(userId),\n * });\n *\n * // Reactive\n * const query = createQuery(() => userQuery.options);\n *\n * // Imperative\n * const { data, error } = await userQuery.fetch();\n * ```\n */\nexport function createQueryFactories(queryClient: QueryClient) {\n\tconst defineQuery = <\n\t\tTQueryFnData = unknown,\n\t\tTError = DefaultError,\n\t\tTData = TQueryFnData,\n\t\tTQueryData = TQueryFnData,\n\t\tconst TQueryKey extends QueryKey = QueryKey,\n\t>(\n\t\tinput: QueryOptionsInput<\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 options = queryOptions(input);\n\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>(options),\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\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>(options),\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\treturn {\n\t\t\toptions,\n\t\t\tfetch,\n\t\t\tensure,\n\t\t};\n\t};\n\n\tconst defineMutation = <\n\t\tTData,\n\t\tTError,\n\t\tTVariables = void,\n\t\tTContext = unknown,\n\t\tconst TMutationKey extends MutationKey = MutationKey,\n\t>(\n\t\tinput: MutationOptionsInput<\n\t\t\tTData,\n\t\t\tTError,\n\t\t\tTVariables,\n\t\t\tTContext,\n\t\t\tTMutationKey\n\t\t>,\n\t): DefineMutationOutput<TData, TError, TVariables, TContext> => {\n\t\tconst options = mutationOptions(input);\n\n\t\tasync function run(variables: TVariables) {\n\t\t\ttry {\n\t\t\t\treturn Ok(await runMutation(queryClient, options, 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\treturn Object.assign(run, {\n\t\t\toptions,\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\n * mutation cache. Powers the callable behavior on mutations returned from\n * `defineMutation`.\n *\n * @internal\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\n/**\n * Identity helper for declaring a TanStack Query key map while preserving\n * tuple types.\n *\n * - **Static entries** like `['users', 'active']` are narrowed to readonly\n * tuples with full literal precision (e.g. `readonly ['users', 'active']`)\n * via the `const` type parameter modifier. No per-line `as const` needed.\n *\n * - **Factory entries** like `(id: string) => ['users', id]` are narrowed to\n * tuple SHAPE (e.g. `[string, string]` with correct arity), not widened to\n * `string[]`. This happens because the strict tuple constraint provides\n * contextual typing into the function body. Literal positions still widen\n * without `as const` (TS does not propagate literal narrowing through\n * contextual typing). Add `as const` to the body when you need the literal:\n * `(id) => ['users', id] as const` -> `readonly ['users', string]`.\n *\n * Empty arrays and non-key values are rejected at the type level.\n *\n * @example\n * ```ts\n * const userKeys = defineKeys({\n * all: ['users'], // readonly ['users']\n * active: ['users', 'active'], // readonly ['users', 'active']\n * detail: (id: string) => ['users', id], // [string, string] (tuple shape kept)\n * page: (n: number) => ['users', n] as const, // readonly ['users', number]\n * });\n * ```\n */\nexport function defineKeys<\n\tconst TKeys extends Record<\n\t\tstring,\n\t\t| readonly [unknown, ...unknown[]]\n\t\t| ((...args: never[]) => readonly [unknown, ...unknown[]])\n\t>,\n>(keys: TKeys): TKeys {\n\treturn keys;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,SAAgB,aAOfA,OAC2E;AAC3E,QAAO;EACN,GAAG;EACH,SAAS,OAAO,YAAY,QAAQ,MAAM,MAAM,QAAQ,QAAQ,CAAC;CACjE;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAgB,gBAOfC,OAO+D;AAC/D,QAAO;EACN,GAAG;EACH,YAAY,OAAOC,cAClB,QAAQ,MAAM,MAAM,WAAW,UAAU,CAAC;CAC3C;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8ED,SAAgB,qBAAqBC,aAA0B;CAC9D,MAAM,cAAc,CAOnBC,UAO2E;EAC3E,MAAM,UAAU,aAAa,MAAM;EAEnC,eAAe,QAA6C;AAC3D,OAAI;AACH,WAAO,GACN,MAAM,YAAY,WAKhB,QAAQ,CACV;GACD,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;EAED,eAAe,SAA8C;AAC5D,OAAI;AACH,WAAO,GACN,MAAM,YAAY,gBAKhB,QAAQ,CACV;GACD,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;AAED,SAAO;GACN;GACA;GACA;EACA;CACD;CAED,MAAM,iBAAiB,CAOtBC,UAO+D;EAC/D,MAAM,UAAU,gBAAgB,MAAM;EAEtC,eAAe,IAAIH,WAAuB;AACzC,OAAI;AACH,WAAO,GAAG,MAAM,YAAY,aAAa,SAAS,UAAU,CAAC;GAC7D,SAAQ,OAAO;AACf,WAAO,IAAI,MAAgB;GAC3B;EACD;AAED,SAAO,OAAO,OAAO,KAAK,EACzB,QACA,EAAC;CACF;AAED,QAAO;EACN;EACA;CACA;AACD;;;;;;;;AASD,SAAS,YACRC,aACAG,SACAJ,WACC;CACD,MAAM,WAAW,YAAY,kBAAkB,CAAC,MAAM,aAAa,QAAQ;AAC3E,QAAO,SAAS,QAAQ,UAAU;AAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,WAMdK,MAAoB;AACrB,QAAO;AACP"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wellcrafted",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
5
  "type": "module",
6
6
  "files": [