@wisemen/vue-core-api-utils 1.0.0-beta.0 → 1.0.0-beta.2

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/dist/index.d.mts CHANGED
@@ -1,52 +1,115 @@
1
+ import { Result } from "neverthrow";
2
+ import { QueryClient } from "@tanstack/vue-query";
1
3
  import * as vue0 from "vue";
2
4
  import { ComputedRef, MaybeRef, Ref, UnwrapRef } from "vue";
3
- import { Result } from "neverthrow";
4
5
 
5
- //#region src/types/queryKeys.type.d.ts
6
- interface QueryKeys {}
7
- //#endregion
8
- //#region src/composables/mutation/mutation.composable.d.ts
9
- type RequestParams<TReqData, TParams> = TReqData extends void ? TParams extends void ? void : {
10
- params: TParams;
11
- } : TParams extends void ? {
12
- body: TReqData;
13
- } : {
14
- body: TReqData;
15
- params: TParams;
16
- };
17
- interface UseMutationOptions<TParams, TReqData, TResData> {
6
+ //#region src/async-result/asyncResult.d.ts
7
+ /**
8
+ * Base class for AsyncResult - internal use only.
9
+ * Use AsyncResult<T, E> as the public type.
10
+ */
11
+ declare abstract class AsyncResultBase<T, E> {
12
+ protected readonly _error: E | undefined;
13
+ protected readonly _status: 'err' | 'loading' | 'ok';
14
+ protected readonly _value: T | undefined;
15
+ protected constructor(status: 'err' | 'loading' | 'ok', value?: T, error?: E);
18
16
  /**
19
- * Whether to enable debug mode
17
+ * Check if the result is an error (type predicate for narrowing)
20
18
  */
21
- isDebug?: boolean;
19
+ isErr(): this is AsyncResultErr<T, E>;
22
20
  /**
23
- * Function that will be called to perform the mutation
24
- * @param options - Parameters and body for the mutation
25
- * @returns Promise with the response data
21
+ * Check if the result is in loading state (type predicate for narrowing)
26
22
  */
27
- queryFn: (options: RequestParams<TReqData, TParams>) => Promise<TResData>;
23
+ isLoading(): this is AsyncResultLoading<T, E>;
28
24
  /**
29
- * Array of query keys which should be invalidated after mutation is successful
25
+ * Check if the result is a success (type predicate for narrowing)
30
26
  */
31
- queryKeysToInvalidate: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof QueryKeys[TQueryKey]]: (params: TParams, data: TResData) => UnwrapRef<QueryKeys[TQueryKey][TQueryKeyParam]> } };
32
- }
33
- interface UseMutationReturnType<TReqData, TResData, TParams = void> {
27
+ isOk(): this is AsyncResultOk<T, E>;
34
28
  /**
35
- * Whether mutation is loading
29
+ * Map the success value to a new value
36
30
  */
37
- isLoading: ComputedRef<boolean>;
31
+ map<U>(fn: (value: T) => U): AsyncResult<U, E>;
38
32
  /**
39
- * Response data from the mutation
33
+ * Map the error to a new error
40
34
  */
41
- data: ComputedRef<TResData extends unknown[] ? TResData[] : TResData | null>;
35
+ mapErr<F>(fn: (error: E) => F): AsyncResult<T, F>;
42
36
  /**
43
- * Function to execute the mutation
44
- * @param data - Parameters and body for the mutation
45
- * @returns Promise with the response data
37
+ * Pattern match on all three states
38
+ */
39
+ match<U>(handlers: {
40
+ err: (error: E) => U;
41
+ loading: () => U;
42
+ ok: (value: T) => U;
43
+ }): U;
44
+ /**
45
+ * Get the success value, or return null if loading or error.
46
+ * Returns T | null when null is passed as the default value.
47
+ */
48
+ unwrapOr(defaultValue: null): T | null;
49
+ /**
50
+ * Get the success value, or return the default value of type T if loading or error.
51
+ * Returns T when a value of type T is passed as the default value.
46
52
  */
47
- execute: (data: RequestParams<TReqData, TParams>) => Promise<TResData>;
53
+ unwrapOr(defaultValue: T): T;
54
+ }
55
+ /**
56
+ * AsyncResult representing an error state
57
+ */
58
+ declare class AsyncResultErr<T, E> extends AsyncResultBase<T, E> {
59
+ private constructor();
60
+ /** @internal */
61
+ static _create<T, E>(error: E): AsyncResultErr<T, E>;
62
+ /** Get the error value - only available after isErr() check */
63
+ getError(): E;
64
+ getResult(): Result<T, E>;
65
+ }
66
+ /**
67
+ * AsyncResult representing a loading state
68
+ */
69
+ declare class AsyncResultLoading<T, E> extends AsyncResultBase<T, E> {
70
+ private constructor();
71
+ /** @internal */
72
+ static _create<T, E>(): AsyncResultLoading<T, E>;
73
+ getResult(): null;
48
74
  }
49
- declare function useMutation<TReqData = void, TResData = void, TParams = void>(options: UseMutationOptions<TParams, TReqData, TResData>): UseMutationReturnType<TReqData, TResData, TParams>;
75
+ /**
76
+ * AsyncResult representing a success state
77
+ */
78
+ declare class AsyncResultOk<T, E> extends AsyncResultBase<T, E> {
79
+ private constructor();
80
+ /** @internal */
81
+ static _create<T, E>(value: T): AsyncResultOk<T, E>;
82
+ getResult(): Result<T, E>;
83
+ /** Get the success value - only available after isOk() check */
84
+ getValue(): T;
85
+ }
86
+ /**
87
+ * Union type of all AsyncResult states.
88
+ * Use isOk(), isErr(), or isLoading() to narrow to specific state.
89
+ */
90
+ type AsyncResult<T, E> = AsyncResultErr<T, E> | AsyncResultLoading<T, E> | AsyncResultOk<T, E>;
91
+ /**
92
+ * Static factory methods for creating AsyncResult instances.
93
+ * This pattern (same name for type and value) is intentional for ergonomic API.
94
+ */
95
+ declare const AsyncResult: {
96
+ /**
97
+ * Create a failed AsyncResult with error
98
+ */
99
+ readonly err: <T, E>(error: E) => AsyncResultErr<T, E>;
100
+ /**
101
+ * Create an AsyncResult from an existing neverthrow Result
102
+ */
103
+ readonly fromResult: <T, E>(result: Result<T, E>) => AsyncResult<T, E>;
104
+ /**
105
+ * Create a loading AsyncResult
106
+ */
107
+ readonly loading: <T, E>() => AsyncResultLoading<T, E>;
108
+ /**
109
+ * Create a successful AsyncResult with data
110
+ */
111
+ readonly ok: <T, E>(value: T) => AsyncResultOk<T, E>;
112
+ };
50
113
  //#endregion
51
114
  //#region src/types/apiError.type.d.ts
52
115
  interface ApiErrorCodes {}
@@ -75,11 +138,73 @@ type ApiUnexpectedError = Error;
75
138
  type ApiError = ApiExpectedError | ApiUnexpectedError;
76
139
  type ApiResult<T> = Result<T, ApiError>;
77
140
  //#endregion
141
+ //#region src/composables/mutation/mutation.composable.d.ts
142
+ type RequestParams$1<TReqData, TParams> = TReqData extends void ? TParams extends void ? void : {
143
+ params: TParams;
144
+ } : TParams extends void ? {
145
+ body: TReqData;
146
+ } : {
147
+ body: TReqData;
148
+ params: TParams;
149
+ };
150
+ interface UseMutationOptions<TReqData, TResData, TParams = void> {
151
+ /**
152
+ * Whether to enable debug mode
153
+ */
154
+ isDebug?: boolean;
155
+ /**
156
+ * Function that will be called to perform the mutation
157
+ * @param options - Parameters and body for the mutation
158
+ * @returns Promise with ApiResult containing either the response data or an error
159
+ */
160
+ queryFn: (options: RequestParams$1<TReqData, TParams>) => Promise<ApiResult<TResData>>;
161
+ /**
162
+ * Object where each key is a query key to invalidate after mutation succeeds.
163
+ * Each query key can optionally have nested parameter extractors.
164
+ * @example
165
+ * ```typescript
166
+ * queryKeysToInvalidate: {
167
+ * contactDetail: {
168
+ * contactUuid: (params, result) => params.contactUuid,
169
+ * },
170
+ * contactIndex: {},
171
+ * }
172
+ * ```
173
+ */
174
+ queryKeysToInvalidate?: Record<string, Record<string, (params: TParams, data: TResData) => any> | undefined>;
175
+ }
176
+ interface UseMutationReturnType<TReqData, TResData, TParams = void> {
177
+ /**
178
+ * Whether mutation is loading
179
+ */
180
+ isLoading: ComputedRef<boolean>;
181
+ /**
182
+ * Response data from the mutation
183
+ * @deprecated - use `result.value.getValue()` instead
184
+ */
185
+ data: ComputedRef<TResData | null>;
186
+ /**
187
+ * Function to execute the mutation
188
+ * @param data - Parameters and body for the mutation
189
+ * @returns Promise with ApiResult containing either the response data or an error
190
+ */
191
+ execute: (data: RequestParams$1<TReqData, TParams>) => Promise<ApiResult<TResData>>;
192
+ /**
193
+ * Computed result of the mutation
194
+ * Returns an AsyncResult with three states:
195
+ * - loading: use `result.value.isLoading()`
196
+ * - ok: use `result.value.isOk()` and `result.value.getValue()`
197
+ * - err: use `result.value.isErr()` and `result.value.getError()`
198
+ */
199
+ result: ComputedRef<AsyncResult<TResData, ApiError>>;
200
+ }
201
+ declare function useMutation<TReqData = void, TResData = void, TParams = void>(options: UseMutationOptions<TReqData, TResData, TParams>): UseMutationReturnType<TReqData, TResData, TParams>;
202
+ //#endregion
78
203
  //#region src/types/sort.type.d.ts
79
204
  type SortDirection = 'asc' | 'desc';
80
- interface Sort<TKey extends string = string> {
205
+ interface Sort<TKey$1 extends string = string> {
81
206
  direction: SortDirection;
82
- key: TKey;
207
+ key: TKey$1;
83
208
  }
84
209
  //#endregion
85
210
  //#region src/types/query.type.d.ts
@@ -141,8 +266,85 @@ interface PaginatedDataDto<TSchema> {
141
266
  };
142
267
  }
143
268
  //#endregion
269
+ //#region src/types/queryKeys.type.d.ts
270
+ /**
271
+ * Base interface for defining query keys with their associated entities
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * declare module '@wisemen/vue-core-api-utils' {
276
+ * interface QueryKeys {
277
+ * userDetail: {
278
+ * params: { userUuid: ComputedRef<UserUuid> }
279
+ * entity: User
280
+ * }
281
+ * productList: {
282
+ * params: { category: ComputedRef<string> }
283
+ * entity: Product[]
284
+ * }
285
+ * }
286
+ * }
287
+ * ```
288
+ */
289
+ interface QueryKeys {}
290
+ /**
291
+ * Generic helper types for libraries/factories that want to infer query key typing
292
+ * from a user-provided config object (instead of relying on module augmentation).
293
+ */
294
+ /**
295
+ * Extract the entity type from a query-keys config for a specific key.
296
+ */
297
+ type QueryKeyEntityFromConfig<TQueryKeys extends object, TKey$1 extends PropertyKey> = TKey$1 extends keyof TQueryKeys ? TQueryKeys[TKey$1] extends {
298
+ entity: infer E;
299
+ } ? E : never : never;
300
+ /**
301
+ * Extract the params type from a query-keys config for a specific key.
302
+ * Automatically wraps each param in Ref for reactivity.
303
+ */
304
+ type QueryKeyParamsFromConfig<TQueryKeys extends object, TKey$1 extends PropertyKey> = TKey$1 extends keyof TQueryKeys ? TQueryKeys[TKey$1] extends {
305
+ params: infer P;
306
+ } ? { [K in keyof P]: Ref<P[K]> } : void : never;
307
+ /**
308
+ * Extract the raw params type from a query-keys config for a specific key (unwrapped from Computed).
309
+ * Used for optimistic updates which accept plain values.
310
+ */
311
+ type QueryKeyRawParamsFromConfig<TQueryKeys extends object, TKey$1 extends PropertyKey> = TKey$1 extends keyof TQueryKeys ? TQueryKeys[TKey$1] extends {
312
+ params: infer P;
313
+ } ? P : void : never;
314
+ /**
315
+ * Get all keys that have an associated entity in a query-keys config.
316
+ */
317
+ type QueryKeysWithEntityFromConfig<TQueryKeys extends object> = ({ [K in keyof TQueryKeys]: TQueryKeys[K] extends {
318
+ entity: any;
319
+ } ? K : never }[keyof TQueryKeys]) & string;
320
+ /**
321
+ * Extract the parameters object from a query key definition
322
+ */
323
+ type QueryKeyParams<TKey$1 extends keyof QueryKeys> = QueryKeys[TKey$1] extends {
324
+ params: infer P;
325
+ } ? Ref<P> : QueryKeys[TKey$1];
326
+ /**
327
+ * Extract the entity type from a query key definition
328
+ */
329
+ type QueryKeyEntity<TKey$1 extends keyof QueryKeys> = QueryKeys[TKey$1] extends {
330
+ entity: infer E;
331
+ } ? E : never;
332
+ /**
333
+ * Check if a query key has an associated entity
334
+ */
335
+ type HasEntity<TKey$1 extends keyof QueryKeys> = QueryKeys[TKey$1] extends {
336
+ entity: any;
337
+ } ? TKey$1 : never;
338
+ /**
339
+ * Get all query keys that have an associated entity
340
+ */
341
+ type QueryKeysWithEntity = { [K in keyof QueryKeys]: HasEntity<K> }[keyof QueryKeys];
342
+ //#endregion
144
343
  //#region src/composables/query/keysetInfiniteQuery.composable.d.ts
145
344
  type NonOptionalKeys$2<T> = { [K in keyof T]-?: T[K] };
345
+ type ExtractParams$2<T> = T extends {
346
+ params: infer P;
347
+ } ? P : T;
146
348
  interface KeysetInfiniteQueryOptions<TData> {
147
349
  /**
148
350
  * The time in milliseconds after which the query will be considered stale
@@ -169,7 +371,7 @@ interface KeysetInfiniteQueryOptions<TData> {
169
371
  /**
170
372
  * Query key associated with the query
171
373
  */
172
- queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys$2<QueryKeys[TQueryKey]>]: MaybeRef<QueryKeys[TQueryKey][TQueryKeyParam]> } };
374
+ queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys$2<ExtractParams$2<QueryKeys[TQueryKey]>>]: MaybeRef<ExtractParams$2<QueryKeys[TQueryKey]>[TQueryKeyParam]> } };
173
375
  }
174
376
  declare function useKeysetInfiniteQuery<TData>(options: KeysetInfiniteQueryOptions<TData>): {
175
377
  hasNextPage: vue0.ComputedRef<boolean>;
@@ -180,11 +382,14 @@ declare function useKeysetInfiniteQuery<TData>(options: KeysetInfiniteQueryOptio
180
382
  isSuccess: vue0.ComputedRef<boolean>;
181
383
  fetchNextPage: () => Promise<void>;
182
384
  refetch: () => Promise<void>;
183
- result: vue0.ComputedRef<KeysetPaginationResult<TData>>;
385
+ result: vue0.ComputedRef<AsyncResult<KeysetPaginationResponse<TData>, ApiError>>;
184
386
  };
185
387
  //#endregion
186
388
  //#region src/composables/query/offsetInfiniteQuery.composable.d.ts
187
389
  type NonOptionalKeys$1<T> = { [K in keyof T]-?: T[K] };
390
+ type ExtractParams$1<T> = T extends {
391
+ params: infer P;
392
+ } ? P : T;
188
393
  interface OffsetInfiniteQueryOptions<TData> {
189
394
  /**
190
395
  * The time in milliseconds after which the query will be considered stale
@@ -211,7 +416,7 @@ interface OffsetInfiniteQueryOptions<TData> {
211
416
  /**
212
417
  * Query key associated with the query
213
418
  */
214
- queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys$1<QueryKeys[TQueryKey]>]: MaybeRef<QueryKeys[TQueryKey][TQueryKeyParam]> } };
419
+ queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys$1<ExtractParams$1<QueryKeys[TQueryKey]>>]: MaybeRef<ExtractParams$1<QueryKeys[TQueryKey]>[TQueryKeyParam]> } };
215
420
  }
216
421
  declare function useOffsetInfiniteQuery<TData>(options: OffsetInfiniteQueryOptions<TData>): {
217
422
  hasNextPage: vue0.ComputedRef<boolean>;
@@ -222,11 +427,14 @@ declare function useOffsetInfiniteQuery<TData>(options: OffsetInfiniteQueryOptio
222
427
  isSuccess: vue0.ComputedRef<boolean>;
223
428
  fetchNextPage: () => Promise<void>;
224
429
  refetch: () => Promise<void>;
225
- result: vue0.ComputedRef<OffsetPaginationResult<TData>>;
430
+ result: vue0.ComputedRef<AsyncResult<OffsetPaginationResponse<TData>, ApiError>>;
226
431
  };
227
432
  //#endregion
228
433
  //#region src/composables/query/query.composable.d.ts
229
434
  type NonOptionalKeys<T> = { [K in keyof T]-?: T[K] };
435
+ type ExtractParams<T> = T extends {
436
+ params: infer P;
437
+ } ? P : T;
230
438
  interface UseQueryOptions<TResData> {
231
439
  /**
232
440
  * The time in milliseconds after which the query will be considered stale
@@ -254,28 +462,26 @@ interface UseQueryOptions<TResData> {
254
462
  /**
255
463
  * Query key associated with the query
256
464
  */
257
- queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys<QueryKeys[TQueryKey]>]: MaybeRef<QueryKeys[TQueryKey][TQueryKeyParam]> } };
465
+ queryKey: { [TQueryKey in keyof QueryKeys]?: { [TQueryKeyParam in keyof NonOptionalKeys<ExtractParams<QueryKeys[TQueryKey]>>]: MaybeRef<ExtractParams<QueryKeys[TQueryKey]>[TQueryKeyParam]> } };
258
466
  }
259
467
  interface UseQueryReturnType<TResData> {
260
- /**
261
- * Response data
262
- */
263
468
  /**
264
469
  * Whether query has errored at least once
265
- * @deprecated - use `result.isErr()` instead
470
+ * @deprecated - use `result.value.isErr()` instead
266
471
  */
267
472
  isError: ComputedRef<boolean>;
268
473
  /**
269
- * Whether query is currently loading
474
+ * Whether query is currently fetching data, regardless of cache status
270
475
  */
271
476
  isFetching: ComputedRef<boolean>;
272
477
  /**
273
478
  * Whether query is initially loading
479
+ * @deprecated - use `result.value.isLoading()` instead
274
480
  */
275
481
  isLoading: ComputedRef<boolean>;
276
482
  /**
277
483
  * Whether query has been executed successfully
278
- * @deprecated - use `result.isOk()` instead
484
+ * @deprecated - use `result.value.isOk()` instead
279
485
  */
280
486
  isSuccess: ComputedRef<boolean>;
281
487
  /**
@@ -284,10 +490,14 @@ interface UseQueryReturnType<TResData> {
284
490
  refetch: () => Promise<void>;
285
491
  /**
286
492
  * Computed result of the query
287
- * It will return an instance of Result<TResData, ApiError>
288
- * where TResData is the response data and ApiError is the error
493
+ * Returns an AsyncResult with three states:
494
+ * - loading: use `result.value.isLoading()`
495
+ * - ok: use `result.value.isOk()` and `result.value.getValue()`
496
+ * - err: use `result.value.isErr()` and `result.value.getError()`
497
+ *
498
+ * Use `result.value.match({ loading, ok, err })` for exhaustive handling
289
499
  */
290
- result: ComputedRef<ApiResult<TResData> | null>;
500
+ result: ComputedRef<AsyncResult<TResData, ApiError>>;
291
501
  }
292
502
  declare function useQuery<TResData>(options: UseQueryOptions<TResData>): UseQueryReturnType<TResData>;
293
503
  //#endregion
@@ -303,4 +513,309 @@ interface QueryConfig {
303
513
  }
304
514
  declare function setQueryConfig(config: Partial<QueryConfig>): void;
305
515
  //#endregion
306
- export { ApiError, ApiErrorCode, ApiErrorCodes, ApiErrorObject, ApiExpectedError, ApiKnownErrorObject, ApiResult, ApiUnexpectedError, ApiUnknownErrorObject, InfiniteQueryOptions, KeysetInfiniteQueryOptions, KeysetPagination, KeysetPaginationParams, KeysetPaginationResponse, KeysetPaginationResult, OffsetInfiniteQueryOptions, OffsetPagination, OffsetPaginationParams, OffsetPaginationResponse, OffsetPaginationResult, PaginatedDataDto, type QueryConfig, QueryKeys, QueryParams, Sort, SortDirection, UseMutationReturnType, UseQueryOptions, UseQueryReturnType, WithFilterQuery, WithSearchQuery, WithSortQuery, setQueryConfig, useKeysetInfiniteQuery, useMutation, useOffsetInfiniteQuery, usePrefetchQuery, useQuery };
516
+ //#region src/factory/createApiUtils.types.d.ts
517
+ interface CreateApiUtilsOptions {
518
+ queryClient: QueryClient;
519
+ }
520
+ /**
521
+ * Helper type to build the invalidation config for a specific query key
522
+ * Maps the query key's params to optional parameter extractors
523
+ */
524
+ type QueryKeyInvalidationConfig<TQueryKeys extends object, TKey$1 extends keyof TQueryKeys, TParams, TResData> = QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1 & string> extends void ? {} : QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1 & string> extends object ? { [ParamKey in keyof QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1 & string>]?: (params: TParams, data: TResData) => QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1 & string>[ParamKey] } : {};
525
+ type QueryKeysWithArrayEntityFromConfig<TQueryKeys extends object> = ({ [K in keyof TQueryKeys]: TQueryKeys[K] extends {
526
+ entity: any[];
527
+ } ? K : never }[keyof TQueryKeys]) & string;
528
+ type QueryKeyArrayItemFromConfig<TQueryKeys extends object, TKey$1 extends PropertyKey> = QueryKeyEntityFromConfig<TQueryKeys, TKey$1> extends (infer TItem)[] ? TItem : never;
529
+ type ApiUseQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>> = {
530
+ staleTime?: number;
531
+ isDebug?: boolean;
532
+ isEnabled?: MaybeRef<boolean>;
533
+ queryFn: () => Promise<ApiResult<QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>>;
534
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
535
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
536
+ } : {
537
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
538
+ });
539
+ type ApiUsePrefetchQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>> = {
540
+ staleTime?: number;
541
+ queryFn: () => Promise<ApiResult<QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>>;
542
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
543
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
544
+ } : {
545
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
546
+ });
547
+ type ApiUseOffsetInfiniteQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>> = {
548
+ staleTime?: number;
549
+ isEnabled?: MaybeRef<boolean>;
550
+ limit?: number;
551
+ queryFn: (paginationParams: OffsetPaginationParams) => Promise<OffsetPaginationResult<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
552
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
553
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
554
+ } : {
555
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
556
+ });
557
+ type ApiUseOffsetInfinitePrefetchQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>> = {
558
+ staleTime?: number;
559
+ limit?: number;
560
+ queryFn: (paginationParams: OffsetPaginationParams) => Promise<OffsetPaginationResult<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
561
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
562
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
563
+ } : {
564
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
565
+ });
566
+ type ApiUseKeysetInfiniteQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>> = {
567
+ staleTime?: number;
568
+ isEnabled?: MaybeRef<boolean>;
569
+ limit?: number;
570
+ queryFn: (paginationParams: KeysetPaginationParams) => Promise<KeysetPaginationResult<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
571
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
572
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
573
+ } : {
574
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
575
+ });
576
+ type ApiUseKeysetInfinitePrefetchQueryOptions<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>> = {
577
+ staleTime?: number;
578
+ limit?: number;
579
+ queryFn: (paginationParams: KeysetPaginationParams) => Promise<KeysetPaginationResult<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
580
+ } & (QueryKeyParamsFromConfig<TQueryKeys, TKey$1> extends void ? {
581
+ params?: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
582
+ } : {
583
+ params: QueryKeyParamsFromConfig<TQueryKeys, TKey$1>;
584
+ });
585
+ type RequestParams<TReqData, TParams> = TReqData extends void ? TParams extends void ? void : {
586
+ params: TParams;
587
+ } : TParams extends void ? {
588
+ body: TReqData;
589
+ } : {
590
+ body: TReqData;
591
+ params: TParams;
592
+ };
593
+ interface ApiUseMutationOptions<TQueryKeys extends object, TReqData, TResData, TParams = void> {
594
+ /**
595
+ * Whether to enable debug mode
596
+ */
597
+ isDebug?: boolean;
598
+ /**
599
+ * Function that will be called to perform the mutation
600
+ * @param options - Parameters and body for the mutation
601
+ * @returns Promise with ApiResult containing either the response data or an error
602
+ */
603
+ queryFn: (options: RequestParams<TReqData, TParams>) => Promise<ApiResult<TResData>>;
604
+ /**
605
+ * Query keys which should be invalidated after mutation is successful
606
+ * Each key is optional and maps to the query key's specific parameters
607
+ * @example
608
+ * ```typescript
609
+ * queryKeysToInvalidate: {
610
+ * userDetail: {
611
+ * userUuid: (params, result) => params.userUuid,
612
+ * },
613
+ * userList: {},
614
+ * }
615
+ * ```
616
+ */
617
+ queryKeysToInvalidate?: { [TKey in keyof TQueryKeys]?: QueryKeyInvalidationConfig<TQueryKeys, TKey, TParams, TResData> };
618
+ }
619
+ interface CreateApiMutationUtilsReturnType<TQueryKeys extends object> {
620
+ useMutation: <TReqData = void, TResData = void, TParams = void>(options: ApiUseMutationOptions<TQueryKeys, TReqData, TResData, TParams>) => UseMutationReturnType<TReqData, TResData, TParams>;
621
+ }
622
+ //#endregion
623
+ //#region src/factory/createApiInfiniteQueryUtils.d.ts
624
+ interface CreateApiInfiniteQueryUtilsReturnType<TQueryKeys extends object> {
625
+ useKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfiniteQueryOptions<TQueryKeys, TKey$1>) => ReturnType<typeof useKeysetInfiniteQuery<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
626
+ useOffsetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseOffsetInfiniteQueryOptions<TQueryKeys, TKey$1>) => ReturnType<typeof useOffsetInfiniteQuery<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>>>;
627
+ }
628
+ declare function createApiInfiniteQueryUtils<TQueryKeys extends object>(): CreateApiInfiniteQueryUtilsReturnType<TQueryKeys>;
629
+ //#endregion
630
+ //#region src/factory/createApiMutationUtils.d.ts
631
+ declare function createApiMutationUtils<TQueryKeys extends object>(): CreateApiMutationUtilsReturnType<TQueryKeys>;
632
+ //#endregion
633
+ //#region src/utils/optimisticUpdates.d.ts
634
+ /**
635
+ * Predicate function type that takes an entity and returns boolean
636
+ */
637
+ type PredicateFn<TEntity> = TEntity extends any[] ? (item: TEntity[number]) => boolean : (item: TEntity) => boolean;
638
+ /**
639
+ * Type for matching by key-value pair
640
+ */
641
+ type EntityItem<TEntity> = TEntity extends any[] ? TEntity[number] : TEntity;
642
+ type MatchByKeyValue<TEntity> = Partial<{ [K in keyof EntityItem<TEntity>]: MaybeRef<UnwrapRef<EntityItem<TEntity>[K]>> }>;
643
+ /**
644
+ * Options for the "by" parameter - can be a predicate function or key-value object
645
+ */
646
+ type ByOption<TEntity> = MatchByKeyValue<TEntity> | PredicateFn<TEntity> | null | undefined;
647
+ /**
648
+ * Options for optimistic update
649
+ */
650
+ interface OptimisticUpdateOptions<TEntity> {
651
+ /**
652
+ * How to match the entity to update:
653
+ * - function: a predicate that returns true for the entity to update
654
+ * - object: key-value pairs to match (e.g., { id: '123' } or { uuid: 'abc' })
655
+ * - undefined: defaults to matching by 'id' from the value
656
+ */
657
+ by?: ByOption<TEntity>;
658
+ /**
659
+ * The new value to set (for single entities) or merge (for arrays)
660
+ */
661
+ value: TEntity extends any[] ? Partial<TEntity[number]> : Partial<TEntity>;
662
+ }
663
+ /**
664
+ * OptimisticUpdates utility class for type-safe optimistic updates
665
+ */
666
+ declare class OptimisticUpdates<TQueryKeys extends object = QueryKeys> {
667
+ private readonly queryClient;
668
+ constructor(queryClient: QueryClient);
669
+ /**
670
+ * Extract the raw entity from AsyncResult data
671
+ */
672
+ private extractEntityFromAsyncResult;
673
+ private isInfiniteDataLike;
674
+ /**
675
+ * Determine if an item should be updated
676
+ */
677
+ private shouldUpdateItem;
678
+ /**
679
+ * Internal method to update entity based on the "by" option
680
+ */
681
+ private updateEntity;
682
+ /**
683
+ * Wrap a raw entity in an AsyncResult (preserving ok state)
684
+ */
685
+ private wrapEntityInAsyncResult;
686
+ /**
687
+ * Get raw entity data from the query cache
688
+ * Automatically extracts the entity from AsyncResult wrapper
689
+ *
690
+ * When using just a key string:
691
+ * - By default (isExact=false), returns ALL queries with that key as first element
692
+ * - With isExact=true, returns only the query stored as [key]
693
+ *
694
+ * @example
695
+ * ```typescript
696
+ * // Get all userDetail queries (returns array)
697
+ * const allUsers = optimisticUpdates.get('userDetail')
698
+ *
699
+ * // Get exact query stored as ['userDetail']
700
+ * const exactUser = optimisticUpdates.get('userDetail', { isExact: true })
701
+ *
702
+ * // Get specific userDetail query with params
703
+ * const user = optimisticUpdates.get(['userDetail', { userUuid: '123' }] as const)
704
+ * ```
705
+ */
706
+ get<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(queryKey: TKey$1, options?: {
707
+ isExact?: false;
708
+ }): QueryKeyEntityFromConfig<TQueryKeys, TKey$1>[];
709
+ get<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(queryKey: TKey$1, options: {
710
+ isExact: true;
711
+ }): QueryKeyEntityFromConfig<TQueryKeys, TKey$1> | null;
712
+ get<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(queryKey: readonly [TKey$1, Partial<QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1>>]): QueryKeyEntityFromConfig<TQueryKeys, TKey$1> | null;
713
+ /**
714
+ * Invalidate queries to trigger a refetch
715
+ *
716
+ * When using just the key, invalidates ALL queries with that key
717
+ * When using key + params tuple, invalidates SPECIFIC query
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * // Invalidate all userDetail queries
722
+ * await optimisticUpdates.invalidate('userDetail')
723
+ *
724
+ * // Invalidate specific query
725
+ * await optimisticUpdates.invalidate(['userDetail', { userUuid: '123' }] as const)
726
+ * ```
727
+ */
728
+ invalidate<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(key: TKey$1): Promise<void>;
729
+ invalidate<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(keyTuple: readonly [TKey$1, Partial<QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1>>]): Promise<void>;
730
+ /**
731
+ * Set raw entity data in the query cache for a specific query
732
+ * Automatically wraps the entity in AsyncResult
733
+ *
734
+ * Both formats set a single query - just with different key representations:
735
+ * - 'userDetail' sets the query with key ['userDetail']
736
+ * - ['userDetail', { userUuid: '123' }] sets the query with that exact key
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * // Set query with just the key
741
+ * optimisticUpdates.set('userDetail', userData)
742
+ *
743
+ * // Set query with key + params
744
+ * optimisticUpdates.set(['userDetail', { userUuid: '123' }] as const, userData)
745
+ * ```
746
+ */
747
+ set<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(queryKey: TKey$1, entity: QueryKeyEntityFromConfig<TQueryKeys, TKey$1>): void;
748
+ set<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(queryKey: readonly [TKey$1, Partial<QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1>>], entity: QueryKeyEntityFromConfig<TQueryKeys, TKey$1>): void;
749
+ /**
750
+ * Update entity data in the query cache optimistically
751
+ *
752
+ * When using just the key, updates ALL queries with that key
753
+ * When using key + params tuple, updates SPECIFIC query
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * // Update all userDetail queries by id
758
+ * optimisticUpdates.update('userDetail', {
759
+ * value: { id: '123', name: 'John Doe' }
760
+ * })
761
+ *
762
+ * // Update specific query by key + params
763
+ * optimisticUpdates.update(['userDetail', { userUuid: '123' }] as const, {
764
+ * value: { name: 'John Doe' }
765
+ * })
766
+ *
767
+ * // Update using predicates
768
+ * optimisticUpdates.update('userList', {
769
+ * value: { isActive: false },
770
+ * by: (user) => user.email === 'john@example.com'
771
+ * })
772
+ * ```
773
+ */
774
+ update<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>, TEntity extends QueryKeyEntityFromConfig<TQueryKeys, TKey$1> = QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>(key: TKey$1, options: OptimisticUpdateOptions<TEntity>): void;
775
+ update<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>, TEntity extends QueryKeyEntityFromConfig<TQueryKeys, TKey$1> = QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>(keyTuple: readonly [TKey$1, Partial<QueryKeyRawParamsFromConfig<TQueryKeys, TKey$1>>], options: OptimisticUpdateOptions<TEntity>): void;
776
+ }
777
+ /**
778
+ * Create an OptimisticUpdates instance
779
+ */
780
+ declare function createOptimisticUpdates(queryClient: QueryClient): OptimisticUpdates;
781
+ //#endregion
782
+ //#region src/factory/createApiOptimisticUpdatesUtils.d.ts
783
+ interface CreateApiOptimisticUpdatesUtilsReturnType<TQueryKeys extends object> {
784
+ useOptimisticUpdates: () => OptimisticUpdates<TQueryKeys>;
785
+ }
786
+ declare function createApiOptimisticUpdatesUtils<TQueryKeys extends object>(options: CreateApiUtilsOptions): CreateApiOptimisticUpdatesUtilsReturnType<TQueryKeys>;
787
+ //#endregion
788
+ //#region src/factory/createApiPrefetchInfiniteQueryUtils.d.ts
789
+ interface CreateApiPrefetchInfiniteQueryUtilsReturnType<TQueryKeys extends object> {
790
+ usePrefetchKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfinitePrefetchQueryOptions<TQueryKeys, TKey$1>) => {
791
+ execute: () => Promise<void>;
792
+ };
793
+ usePrefetchOffsetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseOffsetInfinitePrefetchQueryOptions<TQueryKeys, TKey$1>) => {
794
+ execute: () => Promise<void>;
795
+ };
796
+ }
797
+ declare function createApiPrefetchInfiniteQueryUtils<TQueryKeys extends object>(options: CreateApiUtilsOptions): CreateApiPrefetchInfiniteQueryUtilsReturnType<TQueryKeys>;
798
+ //#endregion
799
+ //#region src/factory/createApiPrefetchQueryUtils.d.ts
800
+ interface CreateApiPrefetchQueryUtilsReturnType<TQueryKeys extends object> {
801
+ usePrefetchQuery: <TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUsePrefetchQueryOptions<TQueryKeys, TKey$1>) => {
802
+ execute: () => Promise<void>;
803
+ };
804
+ }
805
+ declare function createApiPrefetchQueryUtils<TQueryKeys extends object>(options: CreateApiUtilsOptions): CreateApiPrefetchQueryUtilsReturnType<TQueryKeys>;
806
+ //#endregion
807
+ //#region src/factory/createApiQueryUtils.d.ts
808
+ interface CreateApiQueryUtilsReturnType<TQueryKeys extends object> {
809
+ useQuery: <TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseQueryOptions<TQueryKeys, TKey$1>) => UseQueryReturnType<QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>;
810
+ }
811
+ declare function createApiQueryUtils<TQueryKeys extends object>(): CreateApiQueryUtilsReturnType<TQueryKeys>;
812
+ //#endregion
813
+ //#region src/factory/createApiUtils.d.ts
814
+ type CreateApiUtilsReturnType<TQueryKeys extends object> = ReturnType<typeof createApiQueryUtils<TQueryKeys>> & ReturnType<typeof createApiPrefetchQueryUtils<TQueryKeys>> & ReturnType<typeof createApiInfiniteQueryUtils<TQueryKeys>> & ReturnType<typeof createApiPrefetchInfiniteQueryUtils<TQueryKeys>> & ReturnType<typeof createApiMutationUtils<TQueryKeys>> & ReturnType<typeof createApiOptimisticUpdatesUtils<TQueryKeys>>;
815
+ /**
816
+ * Factory that creates typed composables based on a user-provided query-keys config.
817
+ * This is an alternative to module augmentation of `QueryKeys`.
818
+ */
819
+ declare function createApiUtils<TQueryKeys extends object>(options: CreateApiUtilsOptions): CreateApiUtilsReturnType<TQueryKeys>;
820
+ //#endregion
821
+ export { ApiError, ApiErrorCode, ApiErrorCodes, ApiErrorObject, ApiExpectedError, ApiKnownErrorObject, ApiResult, ApiUnexpectedError, ApiUnknownErrorObject, ApiUseKeysetInfinitePrefetchQueryOptions, ApiUseKeysetInfiniteQueryOptions, ApiUseMutationOptions, ApiUseOffsetInfinitePrefetchQueryOptions, ApiUseOffsetInfiniteQueryOptions, ApiUsePrefetchQueryOptions, ApiUseQueryOptions, AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, type CreateApiInfiniteQueryUtilsReturnType, type CreateApiMutationUtilsReturnType, type CreateApiOptimisticUpdatesUtilsReturnType, type CreateApiPrefetchInfiniteQueryUtilsReturnType, type CreateApiPrefetchQueryUtilsReturnType, type CreateApiQueryUtilsReturnType, type CreateApiUtilsOptions, CreateApiUtilsReturnType, HasEntity, InfiniteQueryOptions, KeysetInfiniteQueryOptions, KeysetPagination, KeysetPaginationParams, KeysetPaginationResponse, KeysetPaginationResult, OffsetInfiniteQueryOptions, OffsetPagination, OffsetPaginationParams, OffsetPaginationResponse, OffsetPaginationResult, OptimisticUpdateOptions, OptimisticUpdates, PaginatedDataDto, type QueryConfig, QueryKeyArrayItemFromConfig, QueryKeyEntity, QueryKeyEntityFromConfig, QueryKeyParams, QueryKeyParamsFromConfig, QueryKeyRawParamsFromConfig, QueryKeys, QueryKeysWithArrayEntityFromConfig, QueryKeysWithEntity, QueryKeysWithEntityFromConfig, QueryParams, Sort, SortDirection, UseMutationReturnType, UseQueryOptions, UseQueryReturnType, WithFilterQuery, WithSearchQuery, WithSortQuery, createApiInfiniteQueryUtils, createApiMutationUtils, createApiOptimisticUpdatesUtils, createApiPrefetchInfiniteQueryUtils, createApiPrefetchQueryUtils, createApiQueryUtils, createApiUtils, createOptimisticUpdates, setQueryConfig, useKeysetInfiniteQuery, useMutation, useOffsetInfiniteQuery, usePrefetchQuery, useQuery };
package/dist/index.mjs CHANGED
@@ -1,15 +1,162 @@
1
- import { useInfiniteQuery, useMutation as useMutation$1, useQuery as useQuery$1, useQueryClient } from "@tanstack/vue-query";
2
- import { computed } from "vue";
3
1
  import { err, ok } from "neverthrow";
2
+ import { useInfiniteQuery, useMutation as useMutation$1, useQuery as useQuery$1, useQueryClient } from "@tanstack/vue-query";
3
+ import { computed, unref } from "vue";
4
+
5
+ //#region src/async-result/asyncResult.ts
6
+ /**
7
+ * Base class for AsyncResult - internal use only.
8
+ * Use AsyncResult<T, E> as the public type.
9
+ */
10
+ var AsyncResultBase = class {
11
+ _error;
12
+ _status;
13
+ _value;
14
+ constructor(status, value, error) {
15
+ this._status = status;
16
+ this._value = value;
17
+ this._error = error;
18
+ }
19
+ /**
20
+ * Check if the result is an error (type predicate for narrowing)
21
+ */
22
+ isErr() {
23
+ return this._status === "err";
24
+ }
25
+ /**
26
+ * Check if the result is in loading state (type predicate for narrowing)
27
+ */
28
+ isLoading() {
29
+ return this._status === "loading";
30
+ }
31
+ /**
32
+ * Check if the result is a success (type predicate for narrowing)
33
+ */
34
+ isOk() {
35
+ return this._status === "ok";
36
+ }
37
+ /**
38
+ * Map the success value to a new value
39
+ */
40
+ map(fn) {
41
+ if (this._status === "loading") return AsyncResult.loading();
42
+ if (this._status === "ok") return AsyncResult.ok(fn(this._value));
43
+ return AsyncResult.err(this._error);
44
+ }
45
+ /**
46
+ * Map the error to a new error
47
+ */
48
+ mapErr(fn) {
49
+ if (this._status === "loading") return AsyncResult.loading();
50
+ if (this._status === "err") return AsyncResult.err(fn(this._error));
51
+ return AsyncResult.ok(this._value);
52
+ }
53
+ /**
54
+ * Pattern match on all three states
55
+ */
56
+ match(handlers) {
57
+ if (this._status === "loading") return handlers.loading();
58
+ if (this._status === "ok") return handlers.ok(this._value);
59
+ return handlers.err(this._error);
60
+ }
61
+ unwrapOr(defaultValue) {
62
+ if (this._status === "ok") return this._value;
63
+ return defaultValue;
64
+ }
65
+ };
66
+ /**
67
+ * AsyncResult representing an error state
68
+ */
69
+ var AsyncResultErr = class AsyncResultErr extends AsyncResultBase {
70
+ constructor(error) {
71
+ super("err", void 0, error);
72
+ }
73
+ /** @internal */
74
+ static _create(error) {
75
+ return new AsyncResultErr(error);
76
+ }
77
+ /** Get the error value - only available after isErr() check */
78
+ getError() {
79
+ return this._error;
80
+ }
81
+ getResult() {
82
+ return err(this._error);
83
+ }
84
+ };
85
+ /**
86
+ * AsyncResult representing a loading state
87
+ */
88
+ var AsyncResultLoading = class AsyncResultLoading extends AsyncResultBase {
89
+ constructor() {
90
+ super("loading");
91
+ }
92
+ /** @internal */
93
+ static _create() {
94
+ return new AsyncResultLoading();
95
+ }
96
+ getResult() {
97
+ return null;
98
+ }
99
+ };
100
+ /**
101
+ * AsyncResult representing a success state
102
+ */
103
+ var AsyncResultOk = class AsyncResultOk extends AsyncResultBase {
104
+ constructor(value) {
105
+ super("ok", value);
106
+ }
107
+ /** @internal */
108
+ static _create(value) {
109
+ return new AsyncResultOk(value);
110
+ }
111
+ getResult() {
112
+ return ok(this._value);
113
+ }
114
+ /** Get the success value - only available after isOk() check */
115
+ getValue() {
116
+ return this._value;
117
+ }
118
+ };
119
+ /**
120
+ * Static factory methods for creating AsyncResult instances.
121
+ * This pattern (same name for type and value) is intentional for ergonomic API.
122
+ */
123
+ const AsyncResult = {
124
+ err(error) {
125
+ return AsyncResultErr._create(error);
126
+ },
127
+ fromResult(result) {
128
+ if (result.isOk()) return AsyncResult.ok(result.value);
129
+ return AsyncResult.err(result.error);
130
+ },
131
+ loading() {
132
+ return AsyncResultLoading._create();
133
+ },
134
+ ok(value) {
135
+ return AsyncResultOk._create(value);
136
+ }
137
+ };
4
138
 
139
+ //#endregion
5
140
  //#region src/composables/mutation/mutation.composable.ts
6
141
  function useMutation(options) {
7
142
  const isDebug = options.isDebug ?? false;
8
143
  const queryClient = useQueryClient();
9
144
  async function onSuccess(responseData, params) {
145
+ if (!options.queryKeysToInvalidate) return;
10
146
  await Promise.all(Object.entries(options.queryKeysToInvalidate).map(async ([queryKey, queryKeyParams]) => {
147
+ if (!queryKeyParams) {
148
+ if (isDebug) console.log(`[MUTATION] Invalidating ${queryKey}`);
149
+ await queryClient.invalidateQueries({ queryKey: [queryKey] });
150
+ return;
151
+ }
11
152
  const qkp = queryKeyParams;
12
- const paramsWithValues = Object.entries(qkp).reduce((acc, [key, value]) => {
153
+ const paramEntries = Object.entries(qkp);
154
+ if (paramEntries.length === 0) {
155
+ if (isDebug) console.log(`[MUTATION] Invalidating ${queryKey}`);
156
+ await queryClient.invalidateQueries({ queryKey: [queryKey] });
157
+ return;
158
+ }
159
+ const paramsWithValues = paramEntries.reduce((acc, [key, value]) => {
13
160
  acc[key] = value(params, responseData);
14
161
  return acc;
15
162
  }, {});
@@ -22,7 +169,9 @@ function useMutation(options) {
22
169
  }
23
170
  const mutation = useMutation$1({
24
171
  mutationFn: options.queryFn,
25
- onSuccess: async (data, variables) => {
172
+ onSuccess: async (result$1, variables) => {
173
+ if (!result$1.isOk()) return;
174
+ const data = result$1.value;
26
175
  if (variables !== void 0 && "params" in variables) {
27
176
  await onSuccess(data, variables.params);
28
177
  return;
@@ -33,20 +182,34 @@ function useMutation(options) {
33
182
  async function execute(data) {
34
183
  return await mutation.mutateAsync(data);
35
184
  }
185
+ const result = computed(() => {
186
+ if (mutation.isPending.value) return AsyncResult.loading();
187
+ if (mutation.isError.value && mutation.error.value) return AsyncResult.err(mutation.error.value);
188
+ if (mutation.isSuccess.value && mutation.data.value !== void 0) {
189
+ const apiResult = mutation.data.value;
190
+ if (apiResult.isOk()) return AsyncResult.ok(apiResult.value);
191
+ if (apiResult.isErr()) return AsyncResult.err(apiResult.error);
192
+ }
193
+ return AsyncResult.loading();
194
+ });
36
195
  return {
37
196
  isLoading: computed(() => mutation.isPending.value),
38
- data: computed(() => mutation.data.value),
39
- execute
197
+ data: computed(() => {
198
+ if (mutation.data.value?.isOk()) return mutation.data.value.value;
199
+ return null;
200
+ }),
201
+ execute,
202
+ result
40
203
  };
41
204
  }
42
205
 
43
206
  //#endregion
44
207
  //#region src/config/config.ts
45
- const DEFAULT_LIMIT$2 = 20;
208
+ const DEFAULT_LIMIT$3 = 20;
46
209
  const DEFAULT_PREFETCH_STALE_TIME = 60;
47
210
  const QUERY_CONFIG = {
48
211
  prefetchStaleTime: DEFAULT_PREFETCH_STALE_TIME,
49
- limit: DEFAULT_LIMIT$2
212
+ limit: DEFAULT_LIMIT$3
50
213
  };
51
214
  function setQueryConfig(config) {
52
215
  if (config.limit != null && config.limit > 0) QUERY_CONFIG.limit = config.limit;
@@ -55,7 +218,7 @@ function setQueryConfig(config) {
55
218
 
56
219
  //#endregion
57
220
  //#region src/composables/query/keysetInfiniteQuery.composable.ts
58
- const DEFAULT_LIMIT$1 = QUERY_CONFIG.limit;
221
+ const DEFAULT_LIMIT$2 = QUERY_CONFIG.limit;
59
222
  function useKeysetInfiniteQuery(options) {
60
223
  function getQueryKey() {
61
224
  const [queryKey, params] = Object.entries(options.queryKey)[0];
@@ -72,7 +235,7 @@ function useKeysetInfiniteQuery(options) {
72
235
  placeholderData: (data) => data,
73
236
  queryFn: ({ pageParam }) => options.queryFn({
74
237
  key: pageParam,
75
- limit: options.limit ?? DEFAULT_LIMIT$1
238
+ limit: options.limit ?? DEFAULT_LIMIT$2
76
239
  }),
77
240
  queryKey: getQueryKey()
78
241
  });
@@ -80,18 +243,17 @@ function useKeysetInfiniteQuery(options) {
80
243
  return Boolean(infiniteQuery.data.value?.pages.find((page) => page.isErr()));
81
244
  });
82
245
  const result = computed(() => {
246
+ if (infiniteQuery.isLoading.value) return AsyncResult.loading();
83
247
  const firstError = infiniteQuery.data.value?.pages.find((page) => page.isErr());
84
- if (firstError) return err(firstError.error);
248
+ if (firstError) return AsyncResult.err(firstError.error);
85
249
  const data = infiniteQuery.data.value?.pages.filter((page) => page.isOk()).flatMap((page) => page.value.data) ?? [];
86
250
  const firstPage = infiniteQuery.data.value?.pages[0];
87
- const meta = firstPage?.isOk() ? firstPage.value.meta : {
88
- next: null,
89
- total: data.length
90
- };
91
- return ok({
251
+ const meta = firstPage?.isOk() ? firstPage.value.meta : { next: null };
252
+ const response = {
92
253
  data,
93
254
  meta: { next: infiniteQuery.hasNextPage.value ? meta.next : null }
94
- });
255
+ };
256
+ return AsyncResult.ok(response);
95
257
  });
96
258
  function fetchNextPage() {
97
259
  if (!infiniteQuery.hasNextPage.value || infiniteQuery.isFetchingNextPage.value) return;
@@ -116,7 +278,7 @@ function useKeysetInfiniteQuery(options) {
116
278
 
117
279
  //#endregion
118
280
  //#region src/composables/query/offsetInfiniteQuery.composable.ts
119
- const DEFAULT_LIMIT = QUERY_CONFIG.limit;
281
+ const DEFAULT_LIMIT$1 = QUERY_CONFIG.limit;
120
282
  function useOffsetInfiniteQuery(options) {
121
283
  function getQueryKey() {
122
284
  const [first] = Object.entries(options.queryKey);
@@ -136,7 +298,7 @@ function useOffsetInfiniteQuery(options) {
136
298
  initialPageParam: 0,
137
299
  placeholderData: (data) => data,
138
300
  queryFn: ({ pageParam }) => options.queryFn({
139
- limit: options.limit ?? DEFAULT_LIMIT,
301
+ limit: options.limit ?? DEFAULT_LIMIT$1,
140
302
  offset: pageParam ?? 0
141
303
  }),
142
304
  queryKey: getQueryKey()
@@ -145,19 +307,21 @@ function useOffsetInfiniteQuery(options) {
145
307
  return Boolean(infiniteQuery.data.value?.pages.find((page) => page.isErr()));
146
308
  });
147
309
  const result = computed(() => {
310
+ if (infiniteQuery.isLoading.value) return AsyncResult.loading();
148
311
  const firstError = infiniteQuery.data.value?.pages.find((page) => page.isErr());
149
- if (firstError) return err(firstError.error);
312
+ if (firstError) return AsyncResult.err(firstError.error);
150
313
  const data = infiniteQuery.data.value?.pages.filter((page) => page.isOk()).flatMap((page) => page.value.data) ?? [];
151
314
  const firstPage = infiniteQuery.data.value?.pages[0];
152
315
  const meta = firstPage?.isOk() ? firstPage.value.meta : null;
153
- return ok({
316
+ const response = {
154
317
  data,
155
318
  meta: {
156
319
  limit: meta?.limit ?? 0,
157
320
  offset: meta?.offset ?? 0,
158
321
  total: meta?.total ?? data.length
159
322
  }
160
- });
323
+ };
324
+ return AsyncResult.ok(response);
161
325
  });
162
326
  function fetchNextPage() {
163
327
  if (!infiniteQuery.hasNextPage.value || infiniteQuery.isFetchingNextPage.value) return;
@@ -208,7 +372,9 @@ function useQuery(options) {
208
372
  staleTime: options.staleTime,
209
373
  enabled: options.isEnabled,
210
374
  placeholderData: (data) => data,
211
- queryFn: options.queryFn,
375
+ queryFn: async () => {
376
+ return AsyncResult.fromResult(await options.queryFn());
377
+ },
212
378
  queryKey: getQueryKey()
213
379
  });
214
380
  function getQueryKey() {
@@ -225,9 +391,319 @@ function useQuery(options) {
225
391
  isLoading: computed(() => query.isLoading.value),
226
392
  isSuccess: computed(() => query.data.value?.isOk() ?? false),
227
393
  refetch,
228
- result: computed(() => query.data.value ?? null)
394
+ result: computed(() => {
395
+ if (query.isLoading.value) return AsyncResult.loading();
396
+ if (query.data.value?.isOk()) return AsyncResult.ok(query.data.value.getValue());
397
+ if (query.data.value?.isErr()) return AsyncResult.err(query.data.value.getError());
398
+ return AsyncResult.loading();
399
+ })
400
+ };
401
+ }
402
+
403
+ //#endregion
404
+ //#region src/factory/createApiInfiniteQueryUtils.ts
405
+ function createApiInfiniteQueryUtils() {
406
+ function useOffsetInfiniteQuery$1(key, queryOptions) {
407
+ const params = queryOptions.params ?? {};
408
+ const queryKey = { [key]: params };
409
+ return useOffsetInfiniteQuery({
410
+ staleTime: queryOptions.staleTime,
411
+ isEnabled: queryOptions.isEnabled,
412
+ limit: queryOptions.limit,
413
+ queryFn: queryOptions.queryFn,
414
+ queryKey
415
+ });
416
+ }
417
+ function useKeysetInfiniteQuery$1(key, queryOptions) {
418
+ const params = queryOptions.params ?? {};
419
+ const queryKey = { [key]: params };
420
+ return useKeysetInfiniteQuery({
421
+ staleTime: queryOptions.staleTime,
422
+ isEnabled: queryOptions.isEnabled,
423
+ limit: queryOptions.limit,
424
+ queryFn: queryOptions.queryFn,
425
+ queryKey
426
+ });
427
+ }
428
+ return {
429
+ useKeysetInfiniteQuery: useKeysetInfiniteQuery$1,
430
+ useOffsetInfiniteQuery: useOffsetInfiniteQuery$1
431
+ };
432
+ }
433
+
434
+ //#endregion
435
+ //#region src/factory/createApiMutationUtils.ts
436
+ function createApiMutationUtils() {
437
+ function useMutation$2(options) {
438
+ return useMutation({
439
+ isDebug: options.isDebug,
440
+ queryFn: options.queryFn,
441
+ queryKeysToInvalidate: options.queryKeysToInvalidate ?? {}
442
+ });
443
+ }
444
+ return { useMutation: useMutation$2 };
445
+ }
446
+
447
+ //#endregion
448
+ //#region src/utils/optimisticUpdates.ts
449
+ /**
450
+ * OptimisticUpdates utility class for type-safe optimistic updates
451
+ */
452
+ var OptimisticUpdates = class {
453
+ constructor(queryClient) {
454
+ this.queryClient = queryClient;
455
+ }
456
+ /**
457
+ * Extract the raw entity from AsyncResult data
458
+ */
459
+ extractEntityFromAsyncResult(data) {
460
+ if (data === void 0 || data === null) return null;
461
+ if (typeof data === "object" && "isOk" in data) {
462
+ const asyncResult = data;
463
+ if (asyncResult.isOk()) return asyncResult.getValue();
464
+ return null;
465
+ }
466
+ return data;
467
+ }
468
+ isInfiniteDataLike(data) {
469
+ return Boolean(data && typeof data === "object" && "pages" in data && Array.isArray(data.pages));
470
+ }
471
+ /**
472
+ * Determine if an item should be updated
473
+ */
474
+ shouldUpdateItem(by, item, value) {
475
+ if (typeof by === "function") return by(item);
476
+ if (by && typeof by === "object") return Object.entries(by).every(([key, matchValue]) => {
477
+ const itemValue = item[key];
478
+ return unref(itemValue) === unref(matchValue);
479
+ });
480
+ const idFromValue = value.id;
481
+ const itemId = item["id"];
482
+ if (idFromValue !== void 0 && itemId !== void 0) return unref(itemId) === unref(idFromValue);
483
+ return false;
484
+ }
485
+ /**
486
+ * Internal method to update entity based on the "by" option
487
+ */
488
+ updateEntity(by, currentData, value) {
489
+ if (Array.isArray(currentData)) return currentData.map((item) => {
490
+ return this.shouldUpdateItem(by, item, value) ? {
491
+ ...item,
492
+ ...value
493
+ } : item;
494
+ });
495
+ if (this.shouldUpdateItem(by, currentData, value)) return {
496
+ ...currentData,
497
+ ...value
498
+ };
499
+ return currentData;
500
+ }
501
+ /**
502
+ * Wrap a raw entity in an AsyncResult (preserving ok state)
503
+ */
504
+ wrapEntityInAsyncResult(entity) {
505
+ return AsyncResult.ok(entity);
506
+ }
507
+ get(queryKey, options) {
508
+ if (Array.isArray(queryKey)) {
509
+ const data = this.queryClient.getQueryData(queryKey);
510
+ return this.extractEntityFromAsyncResult(data);
511
+ }
512
+ if (options?.isExact ?? false) {
513
+ const normalizedKey = [queryKey];
514
+ const data = this.queryClient.getQueryData(normalizedKey);
515
+ return this.extractEntityFromAsyncResult(data);
516
+ }
517
+ const allQueries = this.queryClient.getQueryCache().findAll({ predicate: (query) => {
518
+ return query.queryKey[0] === queryKey;
519
+ } });
520
+ const results = [];
521
+ for (const query of allQueries) {
522
+ const data = query.state.data;
523
+ const entity = this.extractEntityFromAsyncResult(data);
524
+ if (entity !== null) results.push(entity);
525
+ }
526
+ return results;
527
+ }
528
+ async invalidate(keyOrTuple) {
529
+ const isSpecific = Array.isArray(keyOrTuple);
530
+ const key = isSpecific ? keyOrTuple[0] : keyOrTuple;
531
+ const params = isSpecific ? keyOrTuple[1] : null;
532
+ await this.queryClient.invalidateQueries({ predicate: (query) => {
533
+ const queryKey = query.queryKey;
534
+ if (queryKey[0] !== key) return false;
535
+ if (isSpecific && params && queryKey[1]) return Object.entries(params).every(([paramKey, paramValue]) => {
536
+ return queryKey[1][paramKey] === paramValue;
537
+ });
538
+ return !isSpecific;
539
+ } });
540
+ }
541
+ set(queryKey, entity) {
542
+ const wrappedData = this.wrapEntityInAsyncResult(entity);
543
+ const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey];
544
+ this.queryClient.setQueryData(normalizedKey, wrappedData);
545
+ }
546
+ update(keyOrTuple, options) {
547
+ const by = options.by ?? void 0;
548
+ const value = options.value;
549
+ const isSpecific = Array.isArray(keyOrTuple);
550
+ const key = isSpecific ? keyOrTuple[0] : keyOrTuple;
551
+ const params = isSpecific ? keyOrTuple[1] : null;
552
+ const queries = this.queryClient.getQueryCache().findAll({ predicate: (query) => {
553
+ const queryKey = query.queryKey;
554
+ if (queryKey[0] !== key) return false;
555
+ if (isSpecific && params && queryKey[1]) return Object.entries(params).every(([paramKey, paramValue]) => {
556
+ return queryKey[1][paramKey] === paramValue;
557
+ });
558
+ return !isSpecific;
559
+ } });
560
+ for (const query of queries) {
561
+ const currentData = query.state.data;
562
+ if (this.isInfiniteDataLike(currentData)) {
563
+ const updatedInfiniteData = {
564
+ ...currentData,
565
+ pages: currentData.pages.map((page) => {
566
+ if (page && typeof page === "object" && "map" in page && typeof page.map === "function") return page.map((pageValue) => {
567
+ if (pageValue && typeof pageValue === "object" && Array.isArray(pageValue.data)) return {
568
+ ...pageValue,
569
+ data: this.updateEntity(by, pageValue.data, value)
570
+ };
571
+ return pageValue;
572
+ });
573
+ return page;
574
+ })
575
+ };
576
+ this.queryClient.setQueryData(query.queryKey, updatedInfiniteData);
577
+ continue;
578
+ }
579
+ const rawEntity = this.extractEntityFromAsyncResult(currentData);
580
+ if (rawEntity === null) continue;
581
+ const updatedEntity = this.updateEntity(by, rawEntity, value);
582
+ const wrappedData = this.wrapEntityInAsyncResult(updatedEntity);
583
+ this.queryClient.setQueryData(query.queryKey, wrappedData);
584
+ }
585
+ }
586
+ };
587
+ /**
588
+ * Create an OptimisticUpdates instance
589
+ */
590
+ function createOptimisticUpdates(queryClient) {
591
+ return new OptimisticUpdates(queryClient);
592
+ }
593
+
594
+ //#endregion
595
+ //#region src/factory/createApiOptimisticUpdatesUtils.ts
596
+ function createApiOptimisticUpdatesUtils(options) {
597
+ function useOptimisticUpdates() {
598
+ return new OptimisticUpdates(options.queryClient);
599
+ }
600
+ return { useOptimisticUpdates };
601
+ }
602
+
603
+ //#endregion
604
+ //#region src/factory/createApiPrefetchInfiniteQueryUtils.ts
605
+ const DEFAULT_LIMIT = QUERY_CONFIG.limit;
606
+ function createApiPrefetchInfiniteQueryUtils(options) {
607
+ function usePrefetchOffsetInfiniteQuery(key, queryOptions) {
608
+ const queryKey = [key, queryOptions.params ?? {}];
609
+ async function execute() {
610
+ await options.queryClient.prefetchInfiniteQuery({
611
+ staleTime: queryOptions.staleTime,
612
+ getNextPageParam: (lastPage) => {
613
+ if (lastPage.isErr()) return null;
614
+ const total = lastPage.value.meta.offset + lastPage.value.meta.limit;
615
+ if (total >= lastPage.value.meta.total) return null;
616
+ return total;
617
+ },
618
+ initialPageParam: 0,
619
+ queryFn: ({ pageParam }) => queryOptions.queryFn({
620
+ limit: queryOptions.limit ?? DEFAULT_LIMIT,
621
+ offset: pageParam ?? 0
622
+ }),
623
+ queryKey
624
+ });
625
+ }
626
+ return { execute };
627
+ }
628
+ function usePrefetchKeysetInfiniteQuery(key, queryOptions) {
629
+ const queryKey = [key, queryOptions.params ?? {}];
630
+ async function execute() {
631
+ await options.queryClient.prefetchInfiniteQuery({
632
+ staleTime: queryOptions.staleTime,
633
+ getNextPageParam: (lastPage) => {
634
+ if (lastPage.isErr()) return null;
635
+ const next = lastPage.value.meta.next;
636
+ if (next === null || next === void 0) return null;
637
+ return next;
638
+ },
639
+ initialPageParam: void 0,
640
+ queryFn: ({ pageParam }) => queryOptions.queryFn({
641
+ key: pageParam,
642
+ limit: queryOptions.limit ?? DEFAULT_LIMIT
643
+ }),
644
+ queryKey
645
+ });
646
+ }
647
+ return { execute };
648
+ }
649
+ return {
650
+ usePrefetchKeysetInfiniteQuery,
651
+ usePrefetchOffsetInfiniteQuery
652
+ };
653
+ }
654
+
655
+ //#endregion
656
+ //#region src/factory/createApiPrefetchQueryUtils.ts
657
+ function createApiPrefetchQueryUtils(options) {
658
+ function usePrefetchQuery$1(key, queryOptions) {
659
+ const queryKey = [key, queryOptions.params ?? {}];
660
+ async function execute() {
661
+ await options.queryClient.prefetchQuery({
662
+ staleTime: queryOptions.staleTime ?? QUERY_CONFIG.prefetchStaleTime,
663
+ queryFn: async () => {
664
+ return AsyncResult.fromResult(await queryOptions.queryFn());
665
+ },
666
+ queryKey
667
+ });
668
+ }
669
+ return { execute };
670
+ }
671
+ return { usePrefetchQuery: usePrefetchQuery$1 };
672
+ }
673
+
674
+ //#endregion
675
+ //#region src/factory/createApiQueryUtils.ts
676
+ function createApiQueryUtils() {
677
+ function useQuery$2(key, queryOptions) {
678
+ const params = queryOptions.params ?? {};
679
+ const queryKey = { [key]: params };
680
+ return useQuery({
681
+ staleTime: queryOptions.staleTime,
682
+ isDebug: queryOptions.isDebug,
683
+ isEnabled: queryOptions.isEnabled,
684
+ queryFn: queryOptions.queryFn,
685
+ queryKey
686
+ });
687
+ }
688
+ return { useQuery: useQuery$2 };
689
+ }
690
+
691
+ //#endregion
692
+ //#region src/factory/createApiUtils.ts
693
+ /**
694
+ * Factory that creates typed composables based on a user-provided query-keys config.
695
+ * This is an alternative to module augmentation of `QueryKeys`.
696
+ */
697
+ function createApiUtils(options) {
698
+ return {
699
+ ...createApiQueryUtils(),
700
+ ...createApiPrefetchQueryUtils(options),
701
+ ...createApiPrefetchInfiniteQueryUtils(options),
702
+ ...createApiInfiniteQueryUtils(),
703
+ ...createApiMutationUtils(),
704
+ ...createApiOptimisticUpdatesUtils(options)
229
705
  };
230
706
  }
231
707
 
232
708
  //#endregion
233
- export { setQueryConfig, useKeysetInfiniteQuery, useMutation, useOffsetInfiniteQuery, usePrefetchQuery, useQuery };
709
+ export { AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, OptimisticUpdates, createApiInfiniteQueryUtils, createApiMutationUtils, createApiOptimisticUpdatesUtils, createApiPrefetchInfiniteQueryUtils, createApiPrefetchQueryUtils, createApiQueryUtils, createApiUtils, createOptimisticUpdates, setQueryConfig, useKeysetInfiniteQuery, useMutation, useOffsetInfiniteQuery, usePrefetchQuery, useQuery };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "type": "module",
7
- "version": "1.0.0-beta.0",
7
+ "version": "1.0.0-beta.2",
8
8
  "license": "MIT",
9
9
  "sideEffects": false,
10
10
  "exports": {