@wisemen/vue-core-api-utils 1.0.1 → 1.2.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/dist/index.d.mts CHANGED
@@ -320,7 +320,6 @@ interface UseKeysetInfiniteQueryReturnType<TData, TErrorCode extends string = st
320
320
  */
321
321
  result: ComputedRef<AsyncResult<KeysetPaginationResponse<TData>, ApiError<TErrorCode>>>;
322
322
  }
323
- declare function useKeysetInfiniteQuery<TData, TErrorCode extends string = string>(options: KeysetInfiniteQueryOptions<TData, TErrorCode>): UseKeysetInfiniteQueryReturnType<TData, TErrorCode>;
324
323
  //#endregion
325
324
  //#region src/composables/query/offsetInfiniteQuery.composable.d.ts
326
325
  interface OffsetInfiniteQueryOptions<TData, TErrorCode extends string = string> {
@@ -398,7 +397,6 @@ interface UseOffsetInfiniteQueryReturnType<TData, TErrorCode extends string = st
398
397
  */
399
398
  result: ComputedRef<AsyncResult<OffsetPaginationResponse<TData>, ApiError<TErrorCode>>>;
400
399
  }
401
- declare function useOffsetInfiniteQuery<TData, TErrorCode extends string = string>(options: OffsetInfiniteQueryOptions<TData, TErrorCode>): UseOffsetInfiniteQueryReturnType<TData, TErrorCode>;
402
400
  //#endregion
403
401
  //#region src/composables/query/query.composable.d.ts
404
402
  interface UseQueryOptions<TResData, TErrorCode extends string = string> {
@@ -627,19 +625,19 @@ interface ApiUseMutationOptions<TQueryKeys extends object, TReqData, TResData, T
627
625
  */
628
626
  queryKeysToInvalidate?: { [TKey in keyof TQueryKeys]?: QueryKeyInvalidationConfig<TQueryKeys, TKey, TParams, TResData> };
629
627
  }
630
- interface CreateApiMutationUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
631
- useMutation: <TReqData = void, TResData = void, TParams = void>(options: ApiUseMutationOptions<TQueryKeys, TReqData, TResData, TParams, TErrorCode>) => UseMutationReturnType<TReqData, TResData, TParams>;
632
- }
633
628
  //#endregion
634
629
  //#region src/factory/createApiInfiniteQueryUtils.d.ts
635
630
  interface CreateApiInfiniteQueryUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
636
- useKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => ReturnType<typeof useKeysetInfiniteQuery<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>>;
637
- useOffsetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseOffsetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => ReturnType<typeof useOffsetInfiniteQuery<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>>;
631
+ useKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => UseKeysetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
632
+ useOffsetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseOffsetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => UseOffsetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
638
633
  }
639
- declare function createApiInfiniteQueryUtils<TQueryKeys extends object, TErrorCode extends string = string>(): CreateApiInfiniteQueryUtilsReturnType<TQueryKeys, TErrorCode>;
634
+ type ApiUseKeysetInfiniteQueryReturnType<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>, TErrorCode extends string = string> = UseKeysetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
635
+ type ApiUseOffsetInfiniteQueryReturnType<TQueryKeys extends object, TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>, TErrorCode extends string = string> = UseOffsetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
640
636
  //#endregion
641
637
  //#region src/factory/createApiMutationUtils.d.ts
642
- declare function createApiMutationUtils<TQueryKeys extends object, TErrorCode extends string = string>(): CreateApiMutationUtilsReturnType<TQueryKeys, TErrorCode>;
638
+ interface CreateApiMutationUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
639
+ useMutation: <TReqData = void, TResData = void, TParams = void>(options: ApiUseMutationOptions<TQueryKeys, TReqData, TResData, TParams, TErrorCode>) => UseMutationReturnType<TReqData, TResData, TParams, TErrorCode>;
640
+ }
643
641
  //#endregion
644
642
  //#region src/factory/createApiPrefetchInfiniteQueryUtils.d.ts
645
643
  interface CreateApiPrefetchInfiniteQueryUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
@@ -650,7 +648,6 @@ interface CreateApiPrefetchInfiniteQueryUtilsReturnType<TQueryKeys extends objec
650
648
  execute: () => Promise<void>;
651
649
  };
652
650
  }
653
- declare function createApiPrefetchInfiniteQueryUtils<TQueryKeys extends object, TErrorCode extends string = string>(): CreateApiPrefetchInfiniteQueryUtilsReturnType<TQueryKeys, TErrorCode>;
654
651
  //#endregion
655
652
  //#region src/factory/createApiPrefetchQueryUtils.d.ts
656
653
  interface CreateApiPrefetchQueryUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
@@ -658,7 +655,6 @@ interface CreateApiPrefetchQueryUtilsReturnType<TQueryKeys extends object, TErro
658
655
  execute: () => Promise<void>;
659
656
  };
660
657
  }
661
- declare function createApiPrefetchQueryUtils<TQueryKeys extends object, TErrorCode extends string = string>(): CreateApiPrefetchQueryUtilsReturnType<TQueryKeys, TErrorCode>;
662
658
  //#endregion
663
659
  //#region src/utils/query-client/queryClient.d.ts
664
660
  /**
@@ -678,6 +674,16 @@ interface QueryClientUpdateOptions<TEntity> {
678
674
  */
679
675
  value: (item: EntityItem<TEntity>) => EntityItem<TEntity>;
680
676
  }
677
+ /**
678
+ * Result of an update operation, providing a rollback function
679
+ */
680
+ interface QueryClientUpdateResult {
681
+ /**
682
+ * Reverts the cache entries affected by this update to their state before the update was applied.
683
+ * Safe to call multiple times (subsequent calls are no-ops).
684
+ */
685
+ rollback: () => void;
686
+ }
681
687
  /**
682
688
  * QueryClient utility class for type-safe query operations
683
689
  */
@@ -775,11 +781,14 @@ declare class QueryClient<TQueryKeys extends object> {
775
781
  * @example
776
782
  * ```typescript
777
783
  * // Update a specific user by id
778
- * queryClient.update('userDetail', {
784
+ * const { rollback } = queryClient.update('userDetail', {
779
785
  * by: (user) => user.id === '123',
780
786
  * value: (user) => ({ ...user, name: 'John Doe' })
781
787
  * })
782
788
  *
789
+ * // Revert if the mutation fails
790
+ * rollback()
791
+ *
783
792
  * // Update all electronics products to out of stock
784
793
  * queryClient.update('productList', {
785
794
  * by: (product) => product.category === 'electronics',
@@ -787,21 +796,19 @@ declare class QueryClient<TQueryKeys extends object> {
787
796
  * })
788
797
  * ```
789
798
  */
790
- update<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>, TEntity extends QueryKeyEntityFromConfig<TQueryKeys, TKey$1> = QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>(key: TKey$1, options: QueryClientUpdateOptions<TEntity>): void;
791
- 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: QueryClientUpdateOptions<TEntity>): void;
799
+ update<TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>, TEntity extends QueryKeyEntityFromConfig<TQueryKeys, TKey$1> = QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>(key: TKey$1, options: QueryClientUpdateOptions<TEntity>): QueryClientUpdateResult;
800
+ 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: QueryClientUpdateOptions<TEntity>): QueryClientUpdateResult;
792
801
  }
793
802
  //#endregion
794
803
  //#region src/factory/createApiQueryClientUtils.d.ts
795
804
  interface CreateApiQueryClientUtilsReturnType<TQueryKeys extends object> {
796
805
  useQueryClient: () => QueryClient<TQueryKeys>;
797
806
  }
798
- declare function createApiQueryClientUtils<TQueryKeys extends object>(): CreateApiQueryClientUtilsReturnType<TQueryKeys>;
799
807
  //#endregion
800
808
  //#region src/factory/createApiQueryUtils.d.ts
801
809
  interface CreateApiQueryUtilsReturnType<TQueryKeys extends object, TErrorCode extends string = string> {
802
810
  useQuery: <TKey$1 extends QueryKeysWithEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => UseQueryReturnType<QueryKeyEntityFromConfig<TQueryKeys, TKey$1>>;
803
811
  }
804
- declare function createApiQueryUtils<TQueryKeys extends object, TErrorCode extends string = string>(): CreateApiQueryUtilsReturnType<TQueryKeys, TErrorCode>;
805
812
  //#endregion
806
813
  //#region src/factory/createApiUtils.d.ts
807
814
  /**
@@ -820,7 +827,7 @@ declare function createApiQueryUtils<TQueryKeys extends object, TErrorCode exten
820
827
  */
821
828
  declare function createApiUtils<TQueryKeys extends object, TErrorCode extends string = string>(): {
822
829
  useQueryClient: () => QueryClient<TQueryKeys>;
823
- useMutation: <TReqData = void, TResData = void, TParams = void>(options: ApiUseMutationOptions<TQueryKeys, TReqData, TResData, TParams, TErrorCode>) => UseMutationReturnType<TReqData, TResData, TParams, string>;
830
+ useMutation: <TReqData = void, TResData = void, TParams = void>(options: ApiUseMutationOptions<TQueryKeys, TReqData, TResData, TParams, TErrorCode>) => UseMutationReturnType<TReqData, TResData, TParams, TErrorCode>;
824
831
  useKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => UseKeysetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
825
832
  useOffsetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseOffsetInfiniteQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => UseOffsetInfiniteQueryReturnType<QueryKeyArrayItemFromConfig<TQueryKeys, TKey$1>, TErrorCode>;
826
833
  usePrefetchKeysetInfiniteQuery: <TKey$1 extends QueryKeysWithArrayEntityFromConfig<TQueryKeys>>(key: TKey$1, queryOptions: ApiUseKeysetInfinitePrefetchQueryOptions<TQueryKeys, TKey$1, TErrorCode>) => {
@@ -867,4 +874,4 @@ declare class SortUtil {
867
874
  }[];
868
875
  }
869
876
  //#endregion
870
- export { ApiError, ApiErrorObject, ApiExpectedError, ApiKnownErrorObject, ApiResult, ApiUnexpectedError, ApiUnknownErrorObject, ApiUseKeysetInfinitePrefetchQueryOptions, ApiUseKeysetInfiniteQueryOptions, ApiUseMutationOptions, ApiUseOffsetInfinitePrefetchQueryOptions, ApiUseOffsetInfiniteQueryOptions, ApiUsePrefetchQueryOptions, ApiUseQueryOptions, AsyncApiResult, AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, type CreateApiInfiniteQueryUtilsReturnType, type CreateApiMutationUtilsReturnType, type CreateApiPrefetchInfiniteQueryUtilsReturnType, type CreateApiPrefetchQueryUtilsReturnType, type CreateApiQueryClientUtilsReturnType, type CreateApiQueryUtilsReturnType, type InfiniteQueryOptions, type KeysetInfiniteQueryOptions, KeysetPagination, KeysetPaginationParams, KeysetPaginationResponse, KeysetPaginationResult, type OffsetInfiniteQueryOptions, OffsetPagination, OffsetPaginationParams, OffsetPaginationResponse, OffsetPaginationResult, PaginatedDataDto, QueryClient, QueryClientUpdateOptions, type QueryConfig, QueryKeyArrayItemFromConfig, QueryKeysWithArrayEntityFromConfig, type QueryParams, Sort, SortDirection, SortUtil, type TanstackQueryClient, type UseMutationReturnType, type UseQueryOptions, type UseQueryReturnType, type WithFilterQuery, type WithSearchQuery, type WithSortQuery, type WithStaticFilterQuery, apiUtilsPlugin, createApiInfiniteQueryUtils, createApiMutationUtils, createApiPrefetchInfiniteQueryUtils, createApiPrefetchQueryUtils, createApiQueryClientUtils, createApiQueryUtils, createApiUtils, getQueryClient as getTanstackQueryClient, initializeApiUtils, setQueryConfig };
877
+ export { type ApiUnexpectedError, type ApiUnknownErrorObject, type ApiUseKeysetInfinitePrefetchQueryOptions, type ApiUseKeysetInfiniteQueryOptions, type ApiUseKeysetInfiniteQueryReturnType, type ApiUseMutationOptions, type ApiUseOffsetInfinitePrefetchQueryOptions, type ApiUseOffsetInfiniteQueryOptions, type ApiUseOffsetInfiniteQueryReturnType, type ApiUsePrefetchQueryOptions, type ApiUseQueryOptions, AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, type CreateApiInfiniteQueryUtilsReturnType, type CreateApiMutationUtilsReturnType, type CreateApiPrefetchInfiniteQueryUtilsReturnType, type CreateApiPrefetchQueryUtilsReturnType, type CreateApiQueryClientUtilsReturnType, type CreateApiQueryUtilsReturnType, type InfiniteQueryOptions, type KeysetInfiniteQueryOptions, type KeysetPagination, type KeysetPaginationParams, type KeysetPaginationResponse, type OffsetInfiniteQueryOptions, type OffsetPagination, type OffsetPaginationParams, type OffsetPaginationResponse, type PaginatedDataDto, QueryClient, type QueryClientUpdateOptions, type QueryClientUpdateResult, type QueryConfig, type QueryKeyArrayItemFromConfig, type QueryKeysWithArrayEntityFromConfig, type QueryParams, type Sort, SortDirection, SortUtil, type TanstackQueryClient, type UseKeysetInfiniteQueryReturnType, type UseMutationReturnType, type UseOffsetInfiniteQueryReturnType, type UseQueryOptions, type UseQueryReturnType, type WithFilterQuery, type WithSearchQuery, type WithSortQuery, type WithStaticFilterQuery, apiUtilsPlugin, createApiUtils, getQueryClient as getTanstackQueryClient, initializeApiUtils, setQueryConfig, type ApiError as "~ApiError", type ApiErrorObject as "~ApiErrorObject", type ApiExpectedError as "~ApiExpectedError", type ApiKnownErrorObject as "~ApiKnownErrorObject", type ApiResult as "~ApiResult", type AsyncApiResult as "~AsyncApiResult", type KeysetPaginationResult as "~KeysetPaginationResult", type OffsetPaginationResult as "~OffsetPaginationResult" };
package/dist/index.mjs CHANGED
@@ -600,9 +600,11 @@ var QueryClient = class {
600
600
  });
601
601
  return !isSpecific;
602
602
  } });
603
+ const snapshots = /* @__PURE__ */ new Map();
603
604
  for (const query of queries) {
604
605
  const currentData = query.state.data;
605
606
  if (this.isInfiniteDataLike(currentData)) {
607
+ snapshots.set(query.queryKey, currentData);
606
608
  const updatedInfiniteData = {
607
609
  ...currentData,
608
610
  pages: currentData.pages.map((page) => {
@@ -616,10 +618,17 @@ var QueryClient = class {
616
618
  if (!isAsyncResult(currentData)) continue;
617
619
  const rawEntity = this.extractEntityFromAsyncResult(currentData);
618
620
  if (rawEntity === null) continue;
621
+ snapshots.set(query.queryKey, currentData);
619
622
  const updatedEntity = this.updateEntity(by, rawEntity, value);
620
623
  const wrappedData = this.wrapEntityInAsyncResult(updatedEntity);
621
624
  this.queryClient.setQueryData(query.queryKey, wrappedData);
622
625
  }
626
+ let rolledBack = false;
627
+ return { rollback: () => {
628
+ if (rolledBack) return;
629
+ rolledBack = true;
630
+ for (const [queryKey, data] of snapshots) this.queryClient.setQueryData(queryKey, data);
631
+ } };
623
632
  }
624
633
  };
625
634
 
@@ -761,4 +770,4 @@ var SortUtil = class {
761
770
  };
762
771
 
763
772
  //#endregion
764
- export { ApiError, ApiErrorObject, ApiExpectedError, ApiKnownErrorObject, ApiResult, ApiUnexpectedError, ApiUnknownErrorObject, AsyncApiResult, AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, KeysetPagination, KeysetPaginationParams, KeysetPaginationResponse, KeysetPaginationResult, OffsetPagination, OffsetPaginationParams, OffsetPaginationResponse, OffsetPaginationResult, PaginatedDataDto, QueryClient, QueryClientUpdateOptions, Sort, SortDirection, SortUtil, apiUtilsPlugin, createApiInfiniteQueryUtils, createApiMutationUtils, createApiPrefetchInfiniteQueryUtils, createApiPrefetchQueryUtils, createApiQueryClientUtils, createApiQueryUtils, createApiUtils, getQueryClient as getTanstackQueryClient, initializeApiUtils, setQueryConfig };
773
+ export { AsyncResult, AsyncResultErr, AsyncResultLoading, AsyncResultOk, QueryClient, SortDirection, SortUtil, apiUtilsPlugin, createApiUtils, getQueryClient as getTanstackQueryClient, initializeApiUtils, setQueryConfig };
package/package.json CHANGED
@@ -4,8 +4,8 @@
4
4
  "access": "public"
5
5
  },
6
6
  "type": "module",
7
- "version": "1.0.1",
8
- "license": "MIT",
7
+ "version": "1.2.0",
8
+ "license": "SEE LICENSE IN LICENSE.md",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "https://github.com/wisemen-digital/wisemen-core",
@@ -22,8 +22,15 @@
22
22
  "module": "./dist/index.mjs",
23
23
  "types": "./dist/index.d.mts",
24
24
  "typings": "./dist/index.d.mts",
25
+ "keywords": [
26
+ "tanstack-intent",
27
+ "vue-query",
28
+ "result",
29
+ "error-handling"
30
+ ],
25
31
  "files": [
26
- "./dist"
32
+ "dist",
33
+ "skills"
27
34
  ],
28
35
  "peerDependencies": {
29
36
  "@tanstack/vue-query": ">=5.90.5",
@@ -31,10 +38,12 @@
31
38
  "vue": ">=3.5.22"
32
39
  },
33
40
  "devDependencies": {
41
+ "@tanstack/intent": "^0.0.29",
34
42
  "@types/node": "24.8.1",
35
43
  "eslint": "9.39.4",
36
44
  "tsdown": "0.18.4",
37
45
  "typescript": "5.9.3",
46
+ "vue": "3.5.27",
38
47
  "vitest": "4.1.0",
39
48
  "@wisemen/eslint-config-vue": "2.1.2"
40
49
  },
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: asyncresult-handling
3
+ description: >
4
+ Three-state AsyncResult type (Loading, Ok, Err), isLoading/isOk/isErr type predicates, getValue/getError accessors, match() pattern matching, map/mapErr transformations, safe value extraction without undefined.
5
+ type: core
6
+ library: vue-core-api-utils
7
+ library_version: "0.0.3"
8
+ sources:
9
+ - "wisemen-digital/wisemen-core:docs/packages/api-utils/pages/concepts/result-types.md"
10
+ - "wisemen-digital/wisemen-core:docs/packages/api-utils/pages/usage/overview.md"
11
+ - "wisemen-digital/wisemen-core:packages/web/api-utils/src/async-result/asyncResult.ts"
12
+ ---
13
+
14
+ # @wisemen/vue-core-api-utils — Handling AsyncResult Types
15
+
16
+ All queries and mutations return `AsyncResult<T, E>` — a type-safe alternative to separate `data`, `error`, and `isLoading` states. AsyncResult is always in one of three states: Loading, Ok, or Err.
17
+
18
+ ## Setup
19
+
20
+ ```typescript
21
+ import { useQuery } from '@/api'
22
+
23
+ const { result } = useQuery('contactDetail', {
24
+ params: { contactUuid: computed(() => '123') },
25
+ queryFn: () => ContactService.getByUuid('123'),
26
+ })
27
+
28
+ // result is a ComputedRef<AsyncResult<Contact, ApiError>>
29
+ // It's always in one of three states:
30
+ // - AsyncResult.Loading()
31
+ // - AsyncResult.Ok(contact: Contact)
32
+ // - AsyncResult.Err(error: ApiError)
33
+ ```
34
+
35
+ ## Core Patterns
36
+
37
+ ### Check state and extract values safely
38
+
39
+ ```typescript
40
+ const { result } = useQuery('contactDetail', { /* ... */ })
41
+
42
+ if (result.value.isLoading()) {
43
+ console.log('Request in flight...')
44
+ } else if (result.value.isOk()) {
45
+ const contact = result.value.getValue()
46
+ console.log('Name:', contact.name) // TypeScript knows contact is Contact
47
+ } else if (result.value.isErr()) {
48
+ const error = result.value.getError()
49
+ console.log('Error:', error.detail)
50
+ }
51
+ ```
52
+
53
+ The type predicates `isLoading()`, `isOk()`, and `isErr()` narrow the type so `getValue()` and `getError()` are safe.
54
+
55
+ ### Pattern match all three states
56
+
57
+ ```typescript
58
+ const { result } = useQuery('contactDetail', { /* ... */ })
59
+
60
+ result.value.match({
61
+ loading: () => <div>Loading...</div>,
62
+ ok: (contact) => <div>Name: {contact.name}</div>,
63
+ err: (error) => <div>Error: {error.detail}</div>,
64
+ })
65
+ ```
66
+
67
+ `match()` is exhaustive — you must handle all three cases or TypeScript errors.
68
+
69
+ ### Transform results with map and mapErr
70
+
71
+ ```typescript
72
+ const { result } = useQuery('contactDetail', { /* ... */ })
73
+
74
+ // Transform the success value
75
+ const contactName = result.value.map(contact => contact.name)
76
+
77
+ // Transform the error
78
+ const errorMessage = result.value.mapErr(error => error.detail)
79
+
80
+ // Chain transformations
81
+ const displayText = result.value
82
+ .map(contact => `Hello, ${contact.name}`)
83
+ .mapErr(error => `Failed: ${error.detail}`)
84
+ .unwrapOr('No data')
85
+ ```
86
+
87
+ `map()` and `mapErr()` return new AsyncResult values, letting you transform without unwrapping.
88
+
89
+ ### Use unwrapOr for fallback values
90
+
91
+ ```typescript
92
+ const { result } = useQuery('contactDetail', { /* ... */ })
93
+
94
+ // Get the value if Ok, otherwise use fallback
95
+ const contact = result.value.unwrapOr(null)
96
+ // Type: Contact | null
97
+
98
+ const name = result.value
99
+ .map(c => c.name)
100
+ .unwrapOr('Unknown')
101
+ // Type: string
102
+ ```
103
+
104
+ ## Common Mistakes
105
+
106
+ ### CRITICAL: Forget to check state before calling getValue/getError
107
+
108
+ ```typescript
109
+ // ❌ Wrong: getValue without isOk check
110
+ const { result } = useQuery('contactDetail', { /* ... */ })
111
+ const contact = result.value.getValue()
112
+ console.log(contact.name) // contact could be null!
113
+ ```
114
+
115
+ ```typescript
116
+ // ✅ Correct: check isOk first
117
+ const { result } = useQuery('contactDetail', { /* ... */ })
118
+ if (result.value.isOk()) {
119
+ const contact = result.value.getValue()
120
+ console.log(contact.name) // Safe!
121
+ }
122
+ ```
123
+
124
+ Calling `getValue()` without `isOk()` returns null if the result is loading or an error. You get no compile error, and the UI renders nothing or crashes at runtime.
125
+
126
+ Source: `docs/packages/api-utils/pages/concepts/result-types.md`
127
+
128
+ ### HIGH: Not handle all three states in match()
129
+
130
+ ```typescript
131
+ // ❌ Wrong: missing loading handler
132
+ result.value.match({
133
+ ok: (data) => <div>{data.name}</div>,
134
+ err: (error) => <div>Error: {error.detail}</div>,
135
+ // Forgot loading!
136
+ })
137
+ ```
138
+
139
+ ```typescript
140
+ // ✅ Correct: handle all three states
141
+ result.value.match({
142
+ loading: () => <div>Loading...</div>,
143
+ ok: (data) => <div>{data.name}</div>,
144
+ err: (error) => <div>Error: {error.detail}</div>,
145
+ })
146
+ ```
147
+
148
+ If you omit a handler, TypeScript errors and the UI renders nothing during the omitted state. The match is exhaustive by design.
149
+
150
+ Source: `docs/packages/api-utils/pages/concepts/result-types.md` Pattern Matching Section
151
+
152
+ ### HIGH: Use state flags (isLoading, isError, isSuccess) instead of AsyncResult state
153
+
154
+ ```typescript
155
+ // ❌ Wrong: mixing old flags with AsyncResult
156
+ const { result, isLoading } = useQuery(...)
157
+ if (isLoading.value) {
158
+ // Show spinner
159
+ } else {
160
+ const data = result.value.getValue() // Could be null!
161
+ }
162
+ ```
163
+
164
+ ```typescript
165
+ // ✅ Correct: use AsyncResult state exclusively
166
+ const { result } = useQuery(...)
167
+ if (result.value.isLoading()) {
168
+ // Show spinner
169
+ } else if (result.value.isOk()) {
170
+ const data = result.value.getValue() // Safe!
171
+ }
172
+ ```
173
+
174
+ Composables export both AsyncResult (exhaustive) and backward-compatible flags (`isLoading`, `isError`, `isSuccess`). Mixing them causes logic bugs where flags say the query is done but the result is still loading.
175
+
176
+ Source: Maintainer interview — library provides both patterns for backward compatibility, but agents should prefer AsyncResult
177
+
178
+ ## Next Steps
179
+
180
+ - [Writing Queries](../writing-queries/SKILL.md) — Fetch single resources with caching
181
+ - [Handling Mutations](../writing-mutations/SKILL.md) — Create/update/delete with result handling
@@ -0,0 +1,222 @@
1
+ ---
2
+ name: cache-management
3
+ description: >
4
+ Type-safe QueryClient with get/set/update/invalidate methods, predicate-based updates, cascade invalidation strategy, shared cache across components, lazy refetch patterns.
5
+ type: core
6
+ library: vue-core-api-utils
7
+ library_version: "0.0.3"
8
+ sources:
9
+ - "wisemen-digital/wisemen-core:docs/packages/api-utils/pages/usage/query-client.md"
10
+ - "wisemen-digital/wisemen-core:packages/web/api-utils/src/utils/query-client/queryClient.ts"
11
+ - "wisemen-digital/wisemen-core:packages/web/api-utils/src/factory/createApiQueryClientUtils.ts"
12
+ ---
13
+
14
+ # @wisemen/vue-core-api-utils — Cache Management
15
+
16
+ Manually read, write, update, and invalidate the query cache using the type-safe `useQueryClient()` composable. This is useful for optimistic updates and strategically invalidating affected queries.
17
+
18
+ ## Setup
19
+
20
+ ```typescript
21
+ import { useQueryClient } from '@/api'
22
+
23
+ const queryClient = useQueryClient()
24
+
25
+ // Get cached data
26
+ const contact = queryClient.get(['contactDetail', { contactUuid: '123' }])
27
+
28
+ // Set cached data
29
+ queryClient.set(
30
+ ['contactDetail', { contactUuid: '123' }],
31
+ updatedContact
32
+ )
33
+
34
+ // Update cached data with a predicate
35
+ queryClient.update('contactList', {
36
+ by: (contact) => contact.id === '123',
37
+ value: (contact) => ({ ...contact, name: 'Updated' }),
38
+ })
39
+
40
+ // Invalidate queries (trigger refetch)
41
+ queryClient.invalidate('contactList')
42
+ ```
43
+
44
+ ## Core Patterns
45
+
46
+ ### Get cached data
47
+
48
+ ```typescript
49
+ const queryClient = useQueryClient()
50
+
51
+ // Get specific query
52
+ const contact = queryClient.get(
53
+ ['contactDetail', { contactUuid: '123' }]
54
+ )
55
+
56
+ // Get all queries with a key
57
+ const allContacts = queryClient.get('contactList')
58
+
59
+ // Get exact query only
60
+ const specificQuery = queryClient.get('contactList', { isExact: true })
61
+ ```
62
+
63
+ Returns the cached data or null if not cached. The QueryClient infers entity type from your query key definition.
64
+
65
+ ### Set cached data
66
+
67
+ ```typescript
68
+ const queryClient = useQueryClient()
69
+
70
+ queryClient.set(
71
+ ['contactDetail', { contactUuid: '123' }],
72
+ { id: '123', name: 'John', email: 'john@email.com' }
73
+ )
74
+
75
+ // For lists, set works with arrays too
76
+ queryClient.set('contactList', [
77
+ { id: '123', name: 'John' },
78
+ { id: '456', name: 'Jane' },
79
+ ])
80
+ ```
81
+
82
+ `set()` replaces all cached data for that query key.
83
+
84
+ ### Update cached data with predicates
85
+
86
+ ```typescript
87
+ const queryClient = useQueryClient()
88
+
89
+ // Update a single item in a list
90
+ queryClient.update('contactList', {
91
+ by: (contact) => contact.id === '123', // Predicate
92
+ value: (contact) => ({ // Transform
93
+ ...contact,
94
+ name: 'Updated John'
95
+ }),
96
+ })
97
+
98
+ // For single entities, the predicate always matches
99
+ queryClient.update('contactDetail', {
100
+ by: (contact) => true, // Always update
101
+ value: (contact) => ({ ...contact, name: 'Updated' }),
102
+ })
103
+ ```
104
+
105
+ QueryClient knows whether the entity is an array or single item, so predicates work transparently on lists.
106
+
107
+ ### Invalidate and refetch
108
+
109
+ ```typescript
110
+ const queryClient = useQueryClient()
111
+
112
+ // Invalidate all queries with this key
113
+ queryClient.invalidate('contactList')
114
+
115
+ // Invalidate specific query
116
+ queryClient.invalidate(['contactDetail', { contactUuid: '123' }])
117
+
118
+ // After invalidation, the next query interaction triggers a refetch
119
+ ```
120
+
121
+ Invalidation marks cached data as stale. The next interaction (component mount, user action) triggers a refetch.
122
+
123
+ ## Common Mistakes
124
+
125
+ ### HIGH: Call update/set without checking data structure (array vs entity)
126
+
127
+ ```typescript
128
+ // ❌ Wrong: treating array like entity
129
+ const queryClient = useQueryClient()
130
+ queryClient.update('contactList', {
131
+ by: (contact) => contact.id === '123',
132
+ value: (contact) => ({ ...contact, name: 'Updated' }),
133
+ })
134
+ // If contactList is Contact[], predicate matches each item individually
135
+ // If you expect single match, this breaks silently
136
+ ```
137
+
138
+ ```typescript
139
+ // ✅ Correct: QueryClient infers type from query key
140
+ const queryClient = useQueryClient()
141
+ // If contactList: { entity: Contact[], ... }
142
+ // QueryClient knows to iterate the array and apply predicate to each item
143
+ queryClient.update('contactList', {
144
+ by: (contact) => contact.id === '123',
145
+ value: (contact) => ({ ...contact, name: 'Updated' }),
146
+ })
147
+ // For single entities: { entity: Contact, ... }
148
+ // QueryClient provides the entity directly to predicate
149
+ ```
150
+
151
+ QueryClient infers data structure from your query key definition, so the same `update()` call works correctly for arrays and single entities. The type system ensures you're using the right predicate signature.
152
+
153
+ Source: `docs/packages/api-utils/pages/usage/query-client.md` Usage
154
+
155
+ ### MEDIUM: Call set() without async loading state; UI flashes stale data
156
+
157
+ ```typescript
158
+ // ❌ Wrong: immediate set without loading indicator
159
+ const queryClient = useQueryClient()
160
+ queryClient.set(['contactDetail', { contactUuid }], updatedData)
161
+ // Cache updated but no indicator that request is pending
162
+ // UI looks responsive but actually has unconfirmed data
163
+ ```
164
+
165
+ ```typescript
166
+ // ✅ Correct: pair optimistic update with mutation result handling
167
+ const queryClient = useQueryClient()
168
+ const original = queryClient.get(['contactDetail', { contactUuid }])
169
+
170
+ // Update cache optimistically
171
+ queryClient.set(['contactDetail', { contactUuid }], newData)
172
+
173
+ // Execute mutation
174
+ const result = await execute(formData)
175
+ if (result.isErr()) {
176
+ // Rollback on error
177
+ queryClient.set(['contactDetail', { contactUuid }], original)
178
+ }
179
+ ```
180
+
181
+ If you update the cache without a mutation in flight, the UI looks responsive but the data is unconfirmed. Always pair cache updates with mutation execution and rollback on error.
182
+
183
+ Source: `docs/packages/api-utils/pages/usage/query-client.md` Real-World Example
184
+
185
+ ## Cache Strategy
186
+
187
+ > Explicitly invalidate only the queries affected by the mutation. Let lazy refetch handle the rest when users navigate to pages needing other data.
188
+ >
189
+ > — Maintainer guidance
190
+
191
+ When a mutation succeeds, look at what changed:
192
+ - If you updated a contact, invalidate `contactDetail` and `contactList` (they both show that contact)
193
+ - If you archived a conversation, invalidate `conversationList` (but maybe not `conversationDetail` unless showing the one you archived)
194
+ - Don't invalidate unrelated queries — let them refetch lazily when needed
195
+
196
+ ## Shared Cache Across Components
197
+
198
+ Important: Multiple components using the same query key share the same cached data. This is a feature, not a bug.
199
+
200
+ ```typescript
201
+ // ComponentA
202
+ const { result: resultA } = useQuery('userDetail', {
203
+ params: { id: computed(() => 'same-id') },
204
+ queryFn: () => UserService.getById('same-id'),
205
+ })
206
+
207
+ // ComponentB
208
+ const { result: resultB } = useQuery('userDetail', {
209
+ params: { id: computed(() => 'same-id') },
210
+ queryFn: () => UserService.getById('same-id'),
211
+ })
212
+
213
+ // resultA and resultB are the SAME cached value
214
+ // Mutation in B invalidates A's cache
215
+ ```
216
+
217
+ Use this to your advantage: invalidate a query and all components using it refetch automatically.
218
+
219
+ ## See Also
220
+
221
+ - [Writing Mutations](../writing-mutations/SKILL.md) — Every mutation needs to know which queries to invalidate
222
+ - [Writing Queries](../writing-queries/SKILL.md) — Understanding caching strategy informs cache management choices