@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,351 @@
|
|
|
1
|
+
import { useMutation, useQuery, UseQueryOptions, UseMutationOptions, QueryKey, FetchQueryOptions } from '@tanstack/react-query'
|
|
2
|
+
import { QueryOperationsOptions, MutationFn, QueryFn, InferMutationParams, InferMutationReturn, InferQueryParams, InferQueryReturn } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builder class for creating type-safe query and mutation operations
|
|
6
|
+
* @template TMutations - Record type containing all registered mutation functions
|
|
7
|
+
* @template TQueries - Record type containing all registered query functions
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* QueryOperations provides a fluent interface for building collections of queries and mutations
|
|
11
|
+
* with full type safety. It acts as a centralized registry for all data operations and provides
|
|
12
|
+
* corresponding React hooks that are automatically typed based on the registered functions.
|
|
13
|
+
*
|
|
14
|
+
* Key features:
|
|
15
|
+
* - Fluent builder pattern for registering operations
|
|
16
|
+
* - Automatic type inference for parameters and return types
|
|
17
|
+
* - Type-safe React hooks generation
|
|
18
|
+
* - Immutable operation registration (returns new instances)
|
|
19
|
+
*/
|
|
20
|
+
export class QueryOperations<
|
|
21
|
+
TMutations,
|
|
22
|
+
TQueries,
|
|
23
|
+
> {
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new QueryOperations instance
|
|
26
|
+
* @param _options - Configuration options including QueryClient
|
|
27
|
+
* @param _mutations - Record of registered mutation functions (internal)
|
|
28
|
+
* @param _queries - Record of registered query functions (internal)
|
|
29
|
+
*/
|
|
30
|
+
constructor(
|
|
31
|
+
private _options: QueryOperationsOptions,
|
|
32
|
+
private _mutations: TMutations = {} as TMutations,
|
|
33
|
+
private _queries: TQueries = {} as TQueries
|
|
34
|
+
) { }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gets all registered mutation functions
|
|
38
|
+
* @returns Readonly record of mutation functions
|
|
39
|
+
*/
|
|
40
|
+
get mutations(): Readonly<TMutations> {
|
|
41
|
+
return this._mutations
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets all registered query functions
|
|
46
|
+
* @returns Readonly record of query functions
|
|
47
|
+
*/
|
|
48
|
+
get queries(): Readonly<TQueries> {
|
|
49
|
+
return this._queries
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Registers a new mutation function
|
|
54
|
+
* @template K - The name/key for the mutation
|
|
55
|
+
* @template T - The input data type for the mutation
|
|
56
|
+
* @template R - The return data type for the mutation
|
|
57
|
+
* @param name - Unique name identifier for the mutation
|
|
58
|
+
* @param fn - The mutation function that performs the operation
|
|
59
|
+
* @returns New QueryOperations instance with the mutation added
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const operations = createQueryOperations({ queryClient })
|
|
64
|
+
* .mutation('createUser', async (userData: CreateUserData) => {
|
|
65
|
+
* return api.post('/users', userData)
|
|
66
|
+
* })
|
|
67
|
+
* .mutation('updateUser', async (userData: UpdateUserData) => {
|
|
68
|
+
* return api.put(`/users/${userData.id}`, userData)
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
mutation<K extends string, T = any, R = any>(
|
|
73
|
+
name: K,
|
|
74
|
+
fn: MutationFn<T, R>
|
|
75
|
+
): QueryOperations<TMutations & Record<K, MutationFn<T, R>>, TQueries> {
|
|
76
|
+
return new QueryOperations(
|
|
77
|
+
this._options,
|
|
78
|
+
{ ...this._mutations, [name]: fn } as TMutations & Record<K, MutationFn<T, R>>,
|
|
79
|
+
this._queries
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Registers a new query function
|
|
85
|
+
* @template K - The name/key for the query
|
|
86
|
+
* @template T - The parameters type for the query
|
|
87
|
+
* @template R - The return data type for the query
|
|
88
|
+
* @param name - Unique name identifier for the query
|
|
89
|
+
* @param fn - The query function that fetches the data
|
|
90
|
+
* @returns New QueryOperations instance with the query added
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const operations = createQueryOperations({ queryClient })
|
|
95
|
+
* .query('getUser', async (userId: string) => {
|
|
96
|
+
* return api.get(`/users/${userId}`)
|
|
97
|
+
* })
|
|
98
|
+
* .query('getUsers', async (filters?: UserFilters) => {
|
|
99
|
+
* return api.get('/users', { params: filters })
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
query<K extends string, T = any, R = any>(
|
|
104
|
+
name: K,
|
|
105
|
+
fn: QueryFn<T, R>
|
|
106
|
+
): QueryOperations<TMutations, TQueries & Record<K, QueryFn<T, R>>> {
|
|
107
|
+
return new QueryOperations(
|
|
108
|
+
this._options,
|
|
109
|
+
this._mutations,
|
|
110
|
+
{ ...this._queries, [name]: fn } as TQueries & Record<K, QueryFn<T, R>>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* React hook for executing mutations with full type safety
|
|
116
|
+
* @template K - The mutation key type
|
|
117
|
+
* @param mutationKey - The name of the registered mutation to use
|
|
118
|
+
* @param options - React Query mutation options (excluding mutationFn and mutationKey)
|
|
119
|
+
* @returns React Query mutation object with inferred types
|
|
120
|
+
*
|
|
121
|
+
* @description
|
|
122
|
+
* This hook automatically provides type-safe parameters and return types based on the
|
|
123
|
+
* registered mutation function. It handles error cases and provides proper TypeScript
|
|
124
|
+
* inference for the mutation data and variables.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const createUserMutation = operations.useMutation('createUser', {
|
|
129
|
+
* onSuccess: (user) => {
|
|
130
|
+
* // 'user' is automatically typed as the return type of createUser
|
|
131
|
+
* console.log('Created user:', user.id)
|
|
132
|
+
* }
|
|
133
|
+
* })
|
|
134
|
+
*
|
|
135
|
+
* // Usage - parameters are type-checked
|
|
136
|
+
* createUserMutation.mutate({ name: 'John', email: 'john@example.com' })
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
useMutation<K extends keyof TMutations>(
|
|
140
|
+
mutationKey: K,
|
|
141
|
+
options?: Omit<
|
|
142
|
+
UseMutationOptions<
|
|
143
|
+
InferMutationReturn<TMutations[K]>,
|
|
144
|
+
Error,
|
|
145
|
+
InferMutationParams<TMutations[K]>
|
|
146
|
+
>,
|
|
147
|
+
'mutationFn'
|
|
148
|
+
>
|
|
149
|
+
) {
|
|
150
|
+
const mutationFn = this._mutations[mutationKey] as MutationFn
|
|
151
|
+
|
|
152
|
+
type TData = InferMutationReturn<TMutations[K]>
|
|
153
|
+
type TVariables = InferMutationParams<TMutations[K]>
|
|
154
|
+
|
|
155
|
+
return useMutation<TData, Error, TVariables>({
|
|
156
|
+
mutationKey: this.getMutationKey(mutationKey),
|
|
157
|
+
mutationFn: async (data: TVariables): Promise<TData> => {
|
|
158
|
+
if (!mutationFn) {
|
|
159
|
+
throw new Error(`Mutation "${String(mutationKey)}" not found`)
|
|
160
|
+
}
|
|
161
|
+
return mutationFn(data) as Promise<TData>
|
|
162
|
+
},
|
|
163
|
+
...options
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* React hook for executing queries with full type safety
|
|
169
|
+
* @template K - The query key type
|
|
170
|
+
* @param queryKey - The name of the registered query to use
|
|
171
|
+
* @param params - Parameters to pass to the query function (optional if query doesn't require params)
|
|
172
|
+
* @param options - React Query options (excluding queryKey and queryFn)
|
|
173
|
+
* @returns React Query query object with inferred types
|
|
174
|
+
*
|
|
175
|
+
* @description
|
|
176
|
+
* This hook automatically provides type-safe parameters and return types based on the
|
|
177
|
+
* registered query function. It generates appropriate query keys and handles parameter
|
|
178
|
+
* validation.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* // Query with parameters
|
|
183
|
+
* const userQuery = operations.useQuery('getUser', 'user-123', {
|
|
184
|
+
* enabled: !!userId
|
|
185
|
+
* })
|
|
186
|
+
*
|
|
187
|
+
* // Query without parameters
|
|
188
|
+
* const usersQuery = operations.useQuery('getUsers', undefined, {
|
|
189
|
+
* refetchInterval: 30000
|
|
190
|
+
* })
|
|
191
|
+
*
|
|
192
|
+
* // Query with optional parameters
|
|
193
|
+
* const filteredUsersQuery = operations.useQuery('getUsers', { status: 'active' })
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
useQuery<K extends keyof TQueries, T = InferQueryReturn<TQueries[K]>>(
|
|
197
|
+
queryKey: K,
|
|
198
|
+
params?: InferQueryParams<TQueries[K]>,
|
|
199
|
+
options?: Omit<
|
|
200
|
+
Partial<UseQueryOptions<
|
|
201
|
+
InferQueryReturn<TQueries[K]>,
|
|
202
|
+
Error,
|
|
203
|
+
T,
|
|
204
|
+
QueryKey
|
|
205
|
+
>>,
|
|
206
|
+
'queryFn'
|
|
207
|
+
>
|
|
208
|
+
) {
|
|
209
|
+
const queryFn = this._queries[queryKey] as QueryFn
|
|
210
|
+
|
|
211
|
+
type TData = InferQueryReturn<TQueries[K]>
|
|
212
|
+
|
|
213
|
+
return useQuery<TData, Error, T, QueryKey>({
|
|
214
|
+
queryKey: this.getQueryKey(queryKey, params),
|
|
215
|
+
queryFn: async (): Promise<TData> => {
|
|
216
|
+
if (!queryFn) {
|
|
217
|
+
throw new Error(`Query "${String(queryKey)}" not found`)
|
|
218
|
+
}
|
|
219
|
+
return queryFn(params as any) as Promise<TData>
|
|
220
|
+
},
|
|
221
|
+
...options
|
|
222
|
+
} as any)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Generates a properly typed query key for React Query
|
|
227
|
+
* @template K - The query key type
|
|
228
|
+
* @param queryKey - The name of the query
|
|
229
|
+
* @param params - Optional parameters for the query
|
|
230
|
+
* @returns Query key array, with params included only when necessary
|
|
231
|
+
*
|
|
232
|
+
* @description
|
|
233
|
+
* This method creates React Query compatible keys that include parameters when present.
|
|
234
|
+
* The return type is conditionally typed based on whether the query requires parameters.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* // Returns ['getUser', 'user-123']
|
|
239
|
+
* const keyWithParams = operations.getQueryKey('getUser', 'user-123')
|
|
240
|
+
*
|
|
241
|
+
* // Returns ['getUsers']
|
|
242
|
+
* const keyWithoutParams = operations.getQueryKey('getUsers')
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
getQueryKey<K extends keyof TQueries>(
|
|
246
|
+
queryKey: K,
|
|
247
|
+
params?: InferQueryParams<TQueries[K]>
|
|
248
|
+
): QueryKey {
|
|
249
|
+
return (params !== undefined ? [queryKey, params] : [queryKey]) as QueryKey
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Generates a mutation key for React Query
|
|
254
|
+
* @template K - The mutation key type
|
|
255
|
+
* @param mutationKey - The name of the mutation
|
|
256
|
+
* @returns Mutation key array containing only the mutation name
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* // Returns ['createUser']
|
|
261
|
+
* const mutationKey = operations.getMutationKey('createUser')
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
getMutationKey<K extends keyof TMutations>(mutationKey: K): QueryKey {
|
|
265
|
+
return [mutationKey] as QueryKey
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Prefetches a query to populate the cache ahead of time
|
|
270
|
+
* @template K - The query key type
|
|
271
|
+
* @param queryKey - The name of the registered query to prefetch
|
|
272
|
+
* @param params - Parameters to pass to the query function (optional if query doesn't require params)
|
|
273
|
+
* @param options - React Query prefetch options
|
|
274
|
+
* @returns Promise that resolves when the prefetch is complete
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* // Prefetch user data when hovering over a user link
|
|
279
|
+
* const handleUserHover = async (userId: string) => {
|
|
280
|
+
* await operations.prefetchQuery('getUser', userId, {
|
|
281
|
+
* staleTime: 5 * 60 * 1000 // 5 minutes
|
|
282
|
+
* })
|
|
283
|
+
* }
|
|
284
|
+
*
|
|
285
|
+
* // Prefetch data on route change
|
|
286
|
+
* useEffect(() => {
|
|
287
|
+
* operations.prefetchQuery('getUsers', { status: 'active' })
|
|
288
|
+
* }, [])
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
prefetchQuery<K extends keyof TQueries, T = InferQueryReturn<TQueries[K]>>(
|
|
292
|
+
queryKey: K,
|
|
293
|
+
params?: InferQueryParams<TQueries[K]>,
|
|
294
|
+
options?: FetchQueryOptions<InferQueryReturn<TQueries[K]>, Error, T, QueryKey, never>
|
|
295
|
+
) {
|
|
296
|
+
const prefetchQueryKey = this.getQueryKey(queryKey, params)
|
|
297
|
+
|
|
298
|
+
const queryFn = this._queries[queryKey] as QueryFn
|
|
299
|
+
|
|
300
|
+
return this._options.queryClient.prefetchQuery<InferQueryReturn<TQueries[K]>, Error, T, QueryKey>({
|
|
301
|
+
queryKey: prefetchQueryKey,
|
|
302
|
+
queryFn: queryFn,
|
|
303
|
+
...options,
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Retrieves cached query data if it exists
|
|
309
|
+
* @template K - The query key type
|
|
310
|
+
* @template T - The expected return type (defaults to inferred query return type)
|
|
311
|
+
* @param queryKey - The name of the registered query
|
|
312
|
+
* @param params - Parameters used when the query was cached (optional if query doesn't require params)
|
|
313
|
+
* @returns The cached data if it exists, undefined otherwise
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* // Get cached user data
|
|
318
|
+
* const cachedUser = operations.getQueryData('getUser', 'user-123')
|
|
319
|
+
* if (cachedUser) {
|
|
320
|
+
* console.log('User already in cache:', cachedUser.name)
|
|
321
|
+
* }
|
|
322
|
+
*
|
|
323
|
+
* // Check if users list is cached before showing loading state
|
|
324
|
+
* const cachedUsers = operations.getQueryData('getUsers')
|
|
325
|
+
* const showSkeleton = !cachedUsers
|
|
326
|
+
*
|
|
327
|
+
* // Access cached data in event handlers
|
|
328
|
+
* const handleUserAction = () => {
|
|
329
|
+
* const currentUser = operations.getQueryData('getCurrentUser')
|
|
330
|
+
* if (currentUser?.role === 'admin') {
|
|
331
|
+
* // Perform admin action
|
|
332
|
+
* }
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
async getQueryData<K extends keyof TQueries, T = InferQueryReturn<TQueries[K]>>(
|
|
337
|
+
queryKey: K,
|
|
338
|
+
params?: InferQueryParams<TQueries[K]>,
|
|
339
|
+
options?: FetchQueryOptions<InferQueryReturn<TQueries[K]>, Error, T, QueryKey, never>,
|
|
340
|
+
) {
|
|
341
|
+
const prefetchQueryKey = this.getQueryKey(queryKey, params)
|
|
342
|
+
|
|
343
|
+
const cachedData = this._options.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
|
|
344
|
+
|
|
345
|
+
if (!cachedData) {
|
|
346
|
+
await this.prefetchQuery(queryKey, params, options)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return this._options.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { QueryClient } from '../../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for QueryOperations
|
|
5
|
+
*/
|
|
6
|
+
export type QueryOperationsOptions = {
|
|
7
|
+
/** The React Query client instance */
|
|
8
|
+
queryClient: QueryClient
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generic mutation function type
|
|
13
|
+
* @template T - The input data type
|
|
14
|
+
* @template R - The return data type
|
|
15
|
+
*/
|
|
16
|
+
export type MutationFn<T = any, R = any> = (data: T) => Promise<R> | R
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generic query function type
|
|
20
|
+
* @template T - The parameters type
|
|
21
|
+
* @template R - The return data type
|
|
22
|
+
*/
|
|
23
|
+
export type QueryFn<T = any, R = any> = (params?: T) => Promise<R> | R
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Utility type to infer mutation function parameters
|
|
27
|
+
* @template T - The mutation function type
|
|
28
|
+
*/
|
|
29
|
+
export type InferMutationParams<T> = T extends MutationFn<infer P, any> ? P : never
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Utility type to infer mutation function return type
|
|
33
|
+
* @template T - The mutation function type
|
|
34
|
+
*/
|
|
35
|
+
export type InferMutationReturn<T> = T extends MutationFn<any, infer R> ? R : never
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Utility type to infer query function parameters
|
|
39
|
+
* @template T - The query function type
|
|
40
|
+
*/
|
|
41
|
+
export type InferQueryParams<T> = T extends QueryFn<infer P, any> ? P : never
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Utility type to infer query function return type
|
|
45
|
+
* @template T - The query function type
|
|
46
|
+
*/
|
|
47
|
+
export type InferQueryReturn<T> = T extends QueryFn<any, infer R> ? R : never
|