@codeleap/query 5.8.3 → 5.8.4
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/package.json +6 -10
- package/package.json.bak +3 -7
- package/src/factors/createQueryManager.ts +38 -0
- package/src/factors/createQueryOperations.ts +37 -0
- package/src/factors/index.ts +2 -0
- package/src/index.ts +2 -8
- package/src/lib/Mutations.ts +280 -0
- package/src/{queryClient.ts → lib/QueryClientEnhanced/index.ts} +24 -72
- package/src/lib/QueryClientEnhanced/types.ts +38 -0
- package/src/lib/QueryKeys.ts +319 -0
- package/src/lib/QueryManager.ts +488 -0
- package/src/lib/QueryOperations/index.ts +351 -0
- package/src/lib/QueryOperations/types.ts +47 -0
- package/src/lib/index.ts +5 -0
- package/src/tests/Mutations.spec.tsx +458 -0
- package/src/tests/QueryManager.spec.tsx +920 -0
- package/src/tests/QueryOperations.spec.tsx +109 -0
- package/src/tests/integration.spec.tsx +551 -0
- package/src/tests/setup.ts +119 -0
- package/src/types/core.ts +33 -0
- package/src/types/create.ts +16 -0
- package/src/types/delete.ts +15 -0
- package/src/types/index.ts +7 -0
- package/src/types/list.ts +24 -0
- package/src/types/retrieve.ts +7 -0
- package/src/types/update.ts +14 -0
- package/src/types/utility.ts +22 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +43 -0
- package/src/QueryManager.ts +0 -954
- package/src/types.ts +0 -199
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { CancelOptions, InfiniteData, InvalidateOptions, InvalidateQueryFilters, Query, QueryFilters, QueryKey, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { TypeGuards } from '@codeleap/types'
|
|
4
|
+
import { ListPaginationResponse, ListSelector, PageParam, QueryClient, QueryItem } from '../types'
|
|
5
|
+
import deepEqual from 'fast-deep-equal'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Class for managing React Query keys and operations for a specific query type
|
|
9
|
+
* @template T - The query item type that extends QueryItem
|
|
10
|
+
* @template F - The filter type used for list queries
|
|
11
|
+
*/
|
|
12
|
+
export class QueryKeys<T extends QueryItem, F> {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new QueryKeys instance
|
|
15
|
+
* @param queryName - The name of the query used as base for all keys
|
|
16
|
+
* @param queryClient - The React Query client instance
|
|
17
|
+
*/
|
|
18
|
+
constructor(
|
|
19
|
+
private queryName: string,
|
|
20
|
+
private queryClient: QueryClient
|
|
21
|
+
) { }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the base query keys for different operations
|
|
25
|
+
* @returns Object containing base query keys for list, retrieve, create, update, and delete operations
|
|
26
|
+
*/
|
|
27
|
+
get keys() {
|
|
28
|
+
return {
|
|
29
|
+
// queries
|
|
30
|
+
list: [this.queryName, 'list'] as QueryKey,
|
|
31
|
+
retrieve: (id: QueryItem['id']) => [this.queryName, 'retrieve', id] as QueryKey,
|
|
32
|
+
|
|
33
|
+
// mutations
|
|
34
|
+
create: [this.queryName, 'create'] as QueryKey,
|
|
35
|
+
update: [this.queryName, 'update'] as QueryKey,
|
|
36
|
+
delete: [this.queryName, 'delete'] as QueryKey,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generates a list query key with optional filters
|
|
42
|
+
* @param filters - Optional filters to include in the query key
|
|
43
|
+
* @returns Query key array with or without filters
|
|
44
|
+
*/
|
|
45
|
+
listKeyWithFilters(filters?: F): QueryKey {
|
|
46
|
+
const hasValidFilters = filters != null && (
|
|
47
|
+
typeof filters !== 'object'
|
|
48
|
+
? Boolean(filters)
|
|
49
|
+
: Object.values(filters).some(value =>
|
|
50
|
+
value != null && (typeof value !== 'string' || value.trim() !== '')
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return hasValidFilters ? [...this.keys.list, filters] : this.keys.list
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* React hook that returns a memoized list query key with filters
|
|
59
|
+
* @param filters - Optional filters to include in the query key
|
|
60
|
+
* @returns Memoized query key array
|
|
61
|
+
*/
|
|
62
|
+
useListKeyWithFilters(filters?: F) {
|
|
63
|
+
return useMemo(() => {
|
|
64
|
+
return this.listKeyWithFilters(filters)
|
|
65
|
+
}, [filters])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* React hook that returns a memoized retrieve query key
|
|
70
|
+
* @param id - The ID of the item to retrieve
|
|
71
|
+
* @returns Memoized query key array for retrieve operation
|
|
72
|
+
*/
|
|
73
|
+
useRetrieveKey(id: QueryItem['id']) {
|
|
74
|
+
return useMemo(() => {
|
|
75
|
+
return this.keys.retrieve(id)
|
|
76
|
+
}, [id])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Predicate function to check if a query belongs to this query name (all operations)
|
|
81
|
+
* @private
|
|
82
|
+
* @param queryName - The query name to match against
|
|
83
|
+
* @param query - The query object to check
|
|
84
|
+
* @returns True if the query matches the query name
|
|
85
|
+
*/
|
|
86
|
+
private predicateQueryKeyAll(queryName: string, query: Query<unknown, Error, unknown, QueryKey>) {
|
|
87
|
+
const queryKey = query?.queryKey?.join('/')
|
|
88
|
+
|
|
89
|
+
return queryKey?.includes(queryName)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Predicate function to check if a query is a list query for this query name
|
|
94
|
+
* @private
|
|
95
|
+
* @param queryName - The query name to match against
|
|
96
|
+
* @param query - The query object to check
|
|
97
|
+
* @param toIgnoreQueryKeys - Query keys to ignore in the matching
|
|
98
|
+
* @returns True if the query is a list query and not in the ignore list
|
|
99
|
+
*/
|
|
100
|
+
private predicateQueryKeyList(queryName: string, query: Query<unknown, Error, unknown, QueryKey>, toIgnoreQueryKeys?: QueryKey | QueryKey[]) {
|
|
101
|
+
const queryKey = query?.queryKey?.join('/')
|
|
102
|
+
|
|
103
|
+
if (!TypeGuards.isNil(toIgnoreQueryKeys)) {
|
|
104
|
+
const ignoreQueryKeys = Array.isArray(toIgnoreQueryKeys?.[0]) ? toIgnoreQueryKeys : [toIgnoreQueryKeys]
|
|
105
|
+
if (ignoreQueryKeys.some(key => deepEqual(query?.queryKey, key))) {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const isListQueryKey = queryKey?.includes(queryName) && queryKey?.includes('list')
|
|
111
|
+
|
|
112
|
+
return isListQueryKey
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Invalidates all queries for this query name
|
|
117
|
+
* @param filters - Optional filters to apply to the invalidation
|
|
118
|
+
* @param options - Optional invalidation options
|
|
119
|
+
* @returns Promise that resolves when invalidation is complete
|
|
120
|
+
*/
|
|
121
|
+
async invalidateAll(filters?: InvalidateQueryFilters<QueryKey>, options?: InvalidateOptions) {
|
|
122
|
+
return this.queryClient.invalidateQueries({
|
|
123
|
+
...filters,
|
|
124
|
+
predicate: (query) => this.predicateQueryKeyAll(this.queryName, query),
|
|
125
|
+
}, options)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Invalidates list queries, optionally with specific filters
|
|
130
|
+
* @param listFilters - Optional filters to target specific list queries
|
|
131
|
+
* @param ignoreQueryKeys - Query keys to ignore during invalidation
|
|
132
|
+
* @param filters - Optional filters to apply to the invalidation
|
|
133
|
+
* @param options - Optional invalidation options
|
|
134
|
+
* @returns Promise that resolves when invalidation is complete
|
|
135
|
+
*/
|
|
136
|
+
async invalidateList(listFilters?: F, ignoreQueryKeys?: QueryKey | QueryKey[], filters?: InvalidateQueryFilters<QueryKey>, options?: InvalidateOptions) {
|
|
137
|
+
if (!!listFilters) {
|
|
138
|
+
const queryKey = this.listKeyWithFilters(listFilters)
|
|
139
|
+
|
|
140
|
+
return this.queryClient.invalidateQueries({ ...filters, queryKey }, options)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this.queryClient.invalidateQueries({
|
|
144
|
+
...filters,
|
|
145
|
+
predicate: (query) => this.predicateQueryKeyList(this.queryName, query, ignoreQueryKeys),
|
|
146
|
+
}, options)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Invalidates a specific retrieve query by ID
|
|
151
|
+
* @param id - The ID of the item to invalidate
|
|
152
|
+
* @param filters - Optional filters to apply to the invalidation
|
|
153
|
+
* @param options - Optional invalidation options
|
|
154
|
+
* @returns Promise that resolves when invalidation is complete
|
|
155
|
+
*/
|
|
156
|
+
async invalidateRetrieve(id: QueryItem['id'], filters?: InvalidateQueryFilters<QueryKey>, options?: InvalidateOptions) {
|
|
157
|
+
const queryKey = this.keys.retrieve(id)
|
|
158
|
+
|
|
159
|
+
return this.queryClient.invalidateQueries({
|
|
160
|
+
...filters,
|
|
161
|
+
queryKey,
|
|
162
|
+
}, options)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Refetches all queries for this query name
|
|
167
|
+
* @param filters - Optional filters to apply to the refetch
|
|
168
|
+
* @param options - Optional refetch options
|
|
169
|
+
* @returns Promise that resolves when refetch is complete
|
|
170
|
+
*/
|
|
171
|
+
async refetchAll(filters?: RefetchQueryFilters<QueryKey>, options?: RefetchOptions) {
|
|
172
|
+
return this.queryClient.refetchQueries({
|
|
173
|
+
...filters,
|
|
174
|
+
predicate: (query) => this.predicateQueryKeyAll(this.queryName, query),
|
|
175
|
+
}, options)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Refetches list queries, optionally with specific filters
|
|
180
|
+
* @param listFilters - Optional filters to target specific list queries
|
|
181
|
+
* @param ignoreQueryKeys - Query keys to ignore during refetch
|
|
182
|
+
* @param filters - Optional filters to apply to the refetch
|
|
183
|
+
* @param options - Optional refetch options
|
|
184
|
+
* @returns Promise that resolves when refetch is complete
|
|
185
|
+
*/
|
|
186
|
+
async refetchList(listFilters?: F, ignoreQueryKeys?: QueryKey | QueryKey[], filters?: RefetchQueryFilters<QueryKey>, options?: RefetchOptions) {
|
|
187
|
+
if (!!listFilters) {
|
|
188
|
+
const queryKey = this.listKeyWithFilters(listFilters)
|
|
189
|
+
|
|
190
|
+
return this.queryClient.refetchQueries({ ...filters, queryKey }, options)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return this.queryClient.refetchQueries({
|
|
194
|
+
...filters,
|
|
195
|
+
predicate: (query) => this.predicateQueryKeyList(this.queryName, query, ignoreQueryKeys),
|
|
196
|
+
}, options)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Refetches a specific retrieve query by ID
|
|
201
|
+
* @param id - The ID of the item to refetch
|
|
202
|
+
* @param filters - Optional filters to apply to the refetch
|
|
203
|
+
* @param options - Optional refetch options
|
|
204
|
+
* @returns Promise that resolves when refetch is complete
|
|
205
|
+
*/
|
|
206
|
+
async refetchRetrieve(id: QueryItem['id'], filters?: RefetchQueryFilters<QueryKey>, options?: RefetchOptions) {
|
|
207
|
+
const queryKey = this.keys.retrieve(id)
|
|
208
|
+
|
|
209
|
+
return this.queryClient.refetchQueries({
|
|
210
|
+
...filters,
|
|
211
|
+
queryKey,
|
|
212
|
+
}, options)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Removes a specific retrieve query data from the cache
|
|
217
|
+
* @param id - The ID of the item to remove from cache
|
|
218
|
+
* @param filters - Optional filters to apply to the removal
|
|
219
|
+
* @returns Promise that resolves when removal is complete
|
|
220
|
+
*/
|
|
221
|
+
async removeRetrieveQueryData(id: QueryItem['id'], filters?: QueryFilters<QueryKey>) {
|
|
222
|
+
const queryKey = this.keys.retrieve(id)
|
|
223
|
+
|
|
224
|
+
return this.queryClient.removeQueries({
|
|
225
|
+
...filters,
|
|
226
|
+
queryKey,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Cancels list queries that are currently in flight
|
|
232
|
+
* @param listFilters - Optional filters to target specific list queries
|
|
233
|
+
* @param ignoreQueryKeys - Query keys to ignore during cancellation
|
|
234
|
+
* @param filters - Optional filters to apply to the cancellation
|
|
235
|
+
* @param options - Optional cancellation options
|
|
236
|
+
* @returns Promise that resolves when cancellation is complete
|
|
237
|
+
*/
|
|
238
|
+
async cancelListQueries(listFilters?: F, ignoreQueryKeys?: QueryKey | QueryKey[], filters?: QueryFilters<QueryKey>, options?: CancelOptions) {
|
|
239
|
+
if (!!listFilters) {
|
|
240
|
+
const queryKey = this.listKeyWithFilters(listFilters)
|
|
241
|
+
|
|
242
|
+
return this.queryClient.cancelQueries({ ...filters, queryKey }, options)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return this.queryClient.cancelQueries({
|
|
246
|
+
...filters,
|
|
247
|
+
predicate: (query) => this.predicateQueryKeyList(this.queryName, query, ignoreQueryKeys),
|
|
248
|
+
}, options)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Gets list data from the cache, including both items array and item map
|
|
253
|
+
* @param filters - Optional filters to target specific list data
|
|
254
|
+
* @returns Object containing items array and itemMap (keyed by ID)
|
|
255
|
+
*/
|
|
256
|
+
getListData(filters?: F) {
|
|
257
|
+
const queryKey = this.listKeyWithFilters(filters)
|
|
258
|
+
|
|
259
|
+
const data = this.queryClient.getQueryData<InfiniteData<ListPaginationResponse<T>, PageParam>>(queryKey)
|
|
260
|
+
|
|
261
|
+
const items = (data?.pages ?? []).flat()
|
|
262
|
+
|
|
263
|
+
const itemMap = items.reduce((acc, item) => {
|
|
264
|
+
acc[item?.id] = item
|
|
265
|
+
return acc
|
|
266
|
+
}, {} as Record<QueryItem['id'], T>)
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
items,
|
|
270
|
+
itemMap,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Gets retrieve data from cache, with fallback to list data
|
|
276
|
+
* @param id - The ID of the item to retrieve
|
|
277
|
+
* @param onlyQueryData - If true, only returns data from the specific retrieve query, not from list data
|
|
278
|
+
* @returns The item data or undefined if not found
|
|
279
|
+
*/
|
|
280
|
+
getRetrieveData(id: QueryItem['id'], onlyQueryData = false): T | undefined {
|
|
281
|
+
if (TypeGuards.isNil(id)) return undefined
|
|
282
|
+
|
|
283
|
+
const queryKey = this.keys.retrieve(id)
|
|
284
|
+
|
|
285
|
+
const queryData = this.queryClient.getQueryData<T>(queryKey)
|
|
286
|
+
|
|
287
|
+
if (!queryData?.id && !onlyQueryData) {
|
|
288
|
+
const { itemMap } = this.getListData()
|
|
289
|
+
|
|
290
|
+
return itemMap?.[id]
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return queryData
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Gets all list queries from the query cache
|
|
298
|
+
* @returns Array of list queries for this query name
|
|
299
|
+
*/
|
|
300
|
+
getAllListQueries() {
|
|
301
|
+
const queries = this.queryClient.getQueryCache().findAll({
|
|
302
|
+
predicate: (query) => this.predicateQueryKeyList(this.queryName, query),
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
return queries as Query<ListPaginationResponse<T>, Error, Omit<ListSelector<T>, 'allItems'>, QueryKey>[]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Factory function to create a new QueryKeys instance
|
|
311
|
+
* @template T - The query item type that extends QueryItem
|
|
312
|
+
* @template F - The filter type used for list queries
|
|
313
|
+
* @param name - The name of the query used as base for all keys
|
|
314
|
+
* @param queryClient - The React Query client instance
|
|
315
|
+
* @returns New QueryKeys instance
|
|
316
|
+
*/
|
|
317
|
+
export const createQueryKeys = <T extends QueryItem, F>(name: string, queryClient: QueryClient) => {
|
|
318
|
+
return new QueryKeys<T, F>(name, queryClient)
|
|
319
|
+
}
|