@codeleap/query 4.3.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/package.json +31 -0
- package/package.json.bak +31 -0
- package/src/QueryManager.ts +954 -0
- package/src/index.ts +9 -0
- package/src/queryClient.ts +260 -0
- package/src/types.ts +199 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import * as ReactQuery from '@tanstack/react-query'
|
|
2
|
+
import { waitFor } from '@codeleap/utils'
|
|
3
|
+
import { QueryManagerOptions, QueryManagerItem } from './types'
|
|
4
|
+
import { QueryManager } from './QueryManager'
|
|
5
|
+
|
|
6
|
+
export type QueryKeyBuilder<Args extends any[] = any[]> = (...args:Args) => ReactQuery.QueryKey
|
|
7
|
+
|
|
8
|
+
type PollingResult<T> = {
|
|
9
|
+
stop: boolean
|
|
10
|
+
data: T
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type PollingCallback<T, R> = (query: ReactQuery.Query<T>, count: number, prev?: R) => Promise<PollingResult<R>>
|
|
14
|
+
|
|
15
|
+
type PollQueryOptions<T, R> = {
|
|
16
|
+
interval: number
|
|
17
|
+
callback: PollingCallback<T, R>
|
|
18
|
+
leading?: boolean
|
|
19
|
+
initialData?: R
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface EnhancedQuery<T> extends ReactQuery.Query<T> {
|
|
23
|
+
waitForRefresh(): Promise<ReactQuery.Query<T>>
|
|
24
|
+
listen(callback: (e: ReactQuery.QueryCacheNotifyEvent) => void): () => void
|
|
25
|
+
refresh(): Promise<T>
|
|
26
|
+
poll<R>(
|
|
27
|
+
options: PollQueryOptions<T, R>
|
|
28
|
+
): Promise<R>
|
|
29
|
+
getData(): T
|
|
30
|
+
ensureData(options?: Partial<ReactQuery.EnsureQueryDataOptions<T, Error, T, ReactQuery.QueryKey, never>>): Promise<T>
|
|
31
|
+
key: ReactQuery.QueryKey
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type DynamicEnhancedQuery<T, BuilderArgs extends any[]> = {
|
|
35
|
+
[P in keyof EnhancedQuery<T>]: (...args: BuilderArgs) => EnhancedQuery<T>[P]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class CodeleapQueryClient {
|
|
39
|
+
constructor(public client: ReactQuery.QueryClient) {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
listenToQuery(key: ReactQuery.QueryKey, callback: (e: ReactQuery.QueryCacheNotifyEvent) => void) {
|
|
43
|
+
const cache = this.client.getQueryCache()
|
|
44
|
+
|
|
45
|
+
const query = cache.find({ exact: true, queryKey: key })
|
|
46
|
+
|
|
47
|
+
if (!query) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const removeListener = cache.subscribe((e) => {
|
|
52
|
+
const matches = ReactQuery.matchQuery({ exact: true, queryKey: key }, e.query)
|
|
53
|
+
|
|
54
|
+
if (matches) {
|
|
55
|
+
callback(e)
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return removeListener
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async pollQuery<T, R>(
|
|
64
|
+
key: ReactQuery.QueryKey,
|
|
65
|
+
options: PollQueryOptions<T, R>,
|
|
66
|
+
) {
|
|
67
|
+
const { interval, callback, initialData, leading = false } = options
|
|
68
|
+
const cache = this.client.getQueryCache()
|
|
69
|
+
|
|
70
|
+
const initialQuery = cache.find({ exact: true, queryKey: key })
|
|
71
|
+
|
|
72
|
+
if (!initialQuery) {
|
|
73
|
+
return Promise.reject(new Error('Query not found'))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let count = 0
|
|
77
|
+
let result: PollingResult<R> = {
|
|
78
|
+
stop: false,
|
|
79
|
+
data: initialData,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
while (!result?.stop) {
|
|
83
|
+
const shouldWait = count > 0 || leading
|
|
84
|
+
|
|
85
|
+
if (shouldWait) {
|
|
86
|
+
await waitFor(interval)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.client.refetchQueries({
|
|
90
|
+
exact: true,
|
|
91
|
+
queryKey: key,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const newQuery = await this.waitForRefresh<T>(key)
|
|
95
|
+
|
|
96
|
+
const newResult = await callback(newQuery, count, result?.data)
|
|
97
|
+
|
|
98
|
+
count += 1
|
|
99
|
+
result = newResult
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result?.data
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
queryProxy<T>(key: ReactQuery.QueryKey) {
|
|
107
|
+
const getClient = () => this
|
|
108
|
+
|
|
109
|
+
return new Proxy<EnhancedQuery<T>>({} as EnhancedQuery<T>, {
|
|
110
|
+
get(target, p, receiver) {
|
|
111
|
+
|
|
112
|
+
const client = getClient()
|
|
113
|
+
|
|
114
|
+
// these don't need the actual query
|
|
115
|
+
switch (p) {
|
|
116
|
+
case 'key':
|
|
117
|
+
return key
|
|
118
|
+
case 'getData':
|
|
119
|
+
return () => {
|
|
120
|
+
return client.client.getQueryData<T>(key)
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const cache = client.client.getQueryCache()
|
|
127
|
+
|
|
128
|
+
const query = cache.find({ exact: true, queryKey: key })
|
|
129
|
+
|
|
130
|
+
if (!query) {
|
|
131
|
+
console.warn(`Attempt to access property ${String(p)} on undefined query with key`, key)
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
switch (p) {
|
|
136
|
+
|
|
137
|
+
case 'waitForRefresh':
|
|
138
|
+
return () => {
|
|
139
|
+
return client.waitForRefresh<T>(key)
|
|
140
|
+
}
|
|
141
|
+
case 'listen':
|
|
142
|
+
return (callback: (e: ReactQuery.QueryCacheNotifyEvent) => void) => {
|
|
143
|
+
return client.listenToQuery(key, callback)
|
|
144
|
+
}
|
|
145
|
+
case 'ensureData':
|
|
146
|
+
return (options) => {
|
|
147
|
+
return client.client.ensureQueryData<T>({
|
|
148
|
+
queryKey: key,
|
|
149
|
+
...options
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
case 'refresh':
|
|
153
|
+
return async () => {
|
|
154
|
+
client.client.refetchQueries({
|
|
155
|
+
exact: true,
|
|
156
|
+
queryKey: key,
|
|
157
|
+
})
|
|
158
|
+
const newQuery = await client.waitForRefresh<T>(key)
|
|
159
|
+
return newQuery.state.data
|
|
160
|
+
}
|
|
161
|
+
case 'poll':
|
|
162
|
+
return (options: PollQueryOptions<T, any>) => {
|
|
163
|
+
return client.pollQuery(key, options)
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
return Reflect.get(query, p, receiver)
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
waitForRefresh<T>(key: ReactQuery.QueryKey) {
|
|
174
|
+
const initialQuery = this.client.getQueryCache().find({ exact: true, queryKey: key })
|
|
175
|
+
|
|
176
|
+
if (!initialQuery) {
|
|
177
|
+
return Promise.reject(new Error('Query not found'))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const updateTime = initialQuery.state.dataUpdatedAt
|
|
181
|
+
const errorTime = initialQuery.state.errorUpdatedAt
|
|
182
|
+
|
|
183
|
+
return new Promise<ReactQuery.Query<T>>((resolve, reject) => {
|
|
184
|
+
const removeListener = this.listenToQuery(key, (e) => {
|
|
185
|
+
const query = e.query
|
|
186
|
+
|
|
187
|
+
const isNewer = query.state.dataUpdatedAt > updateTime || query.state.errorUpdatedAt > errorTime
|
|
188
|
+
|
|
189
|
+
const isIdle = query.state.fetchStatus === 'idle'
|
|
190
|
+
|
|
191
|
+
const isSuccess = query.state.status === 'success'
|
|
192
|
+
const isError = query.state.status === 'error'
|
|
193
|
+
|
|
194
|
+
const isResolved = isSuccess || isError
|
|
195
|
+
|
|
196
|
+
if (isNewer && isIdle && isResolved) {
|
|
197
|
+
if (isSuccess) {
|
|
198
|
+
resolve(query)
|
|
199
|
+
} else {
|
|
200
|
+
reject()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
removeListener()
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
queryKey<Data>(k: ReactQuery.QueryKey, options?: ReactQuery.QueryOptions<Data>) {
|
|
211
|
+
|
|
212
|
+
if(options){
|
|
213
|
+
|
|
214
|
+
this.client.setQueryDefaults(k, options)
|
|
215
|
+
|
|
216
|
+
const cache = this.client.getQueryCache()
|
|
217
|
+
|
|
218
|
+
const q = new ReactQuery.Query({
|
|
219
|
+
cache,
|
|
220
|
+
queryKey: k,
|
|
221
|
+
queryHash: ReactQuery.hashKey(k),
|
|
222
|
+
...options,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
cache.add(q)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this.queryProxy<Data>(k)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
dynamicQueryKey<Data, BuilderArgs extends any[] = any[]>(k: QueryKeyBuilder<BuilderArgs>) {
|
|
232
|
+
|
|
233
|
+
const getClient = () => this
|
|
234
|
+
|
|
235
|
+
return new Proxy<DynamicEnhancedQuery<Data, BuilderArgs>>({} as DynamicEnhancedQuery<Data, BuilderArgs>, {
|
|
236
|
+
get(target, p, receiver) {
|
|
237
|
+
return (...params:BuilderArgs) => {
|
|
238
|
+
const key = k(...params)
|
|
239
|
+
|
|
240
|
+
const proxy = getClient().queryProxy<Data>(key)
|
|
241
|
+
|
|
242
|
+
return Reflect.get(proxy, p, receiver)
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
queryManager<T extends QueryManagerItem, Args>(name:string, options: Partial<QueryManagerOptions<T, Args>>) {
|
|
249
|
+
// @ts-expect-error
|
|
250
|
+
const m = new QueryManager<T, Args>({
|
|
251
|
+
name,
|
|
252
|
+
queryClient: this.client,
|
|
253
|
+
...options,
|
|
254
|
+
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
return m
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { DefinedInitialDataInfiniteOptions, InfiniteData, QueryKey, UseInfiniteQueryResult, UseMutationOptions, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
|
2
|
+
import { QueryManager } from './QueryManager'
|
|
3
|
+
|
|
4
|
+
export type PageParam = {limit: number; offset: number}
|
|
5
|
+
|
|
6
|
+
export type PaginationResponse<T> = {
|
|
7
|
+
count: number
|
|
8
|
+
next: string | null
|
|
9
|
+
previous: string | null
|
|
10
|
+
results: T[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type OmitMutationKeys<O> = Omit<O, 'mutationFn'|'mutationKey'>
|
|
14
|
+
|
|
15
|
+
export type QueryManagerMeta = Record<string, any>
|
|
16
|
+
|
|
17
|
+
export type CreateOptions<T extends QueryManagerItem> = {
|
|
18
|
+
appendTo?: 'start' | 'end' | [number, number] | Record<string, [number, number]>
|
|
19
|
+
optimistic?: boolean
|
|
20
|
+
mutationOptions?: Partial<OmitMutationKeys<UseMutationOptions<T, unknown, Partial<T>, MutationCtx<T>>>>
|
|
21
|
+
onListsWithFilters?: any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type UpdateOptions<T extends QueryManagerItem> = {
|
|
25
|
+
optimistic?: boolean
|
|
26
|
+
|
|
27
|
+
mutationOptions?: Partial<OmitMutationKeys<UseMutationOptions<T, unknown, Partial<T>, MutationCtx<T>>>>
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DeleteOptions<T extends QueryManagerItem> = {
|
|
32
|
+
optimistic?: boolean
|
|
33
|
+
|
|
34
|
+
mutationOptions?: Partial<OmitMutationKeys<UseMutationOptions<T, unknown, T, MutationCtx<T>>>>
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type RetrieveOptions<T extends QueryManagerItem> = {
|
|
39
|
+
queryOptions?: Partial<UseQueryOptions<T, unknown, T>>
|
|
40
|
+
id?: T['id']
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ListOptions<T extends QueryManagerItem, ExtraArgs = any> = {
|
|
44
|
+
queryOptions?: Partial<
|
|
45
|
+
DefinedInitialDataInfiniteOptions<PaginationResponse<T>, Error, UseListSelector<T>, QueryKey, PageParam>
|
|
46
|
+
>
|
|
47
|
+
filter?: ExtraArgs
|
|
48
|
+
limit?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type QueryManagerAction<
|
|
52
|
+
T extends QueryManagerItem,
|
|
53
|
+
ExtraArgs = any,
|
|
54
|
+
Meta extends QueryManagerMeta = QueryManagerMeta,
|
|
55
|
+
Args extends any[] = any[]
|
|
56
|
+
> = (
|
|
57
|
+
manager: QueryManager<T, ExtraArgs, Meta>, ...args: Args
|
|
58
|
+
) => any
|
|
59
|
+
|
|
60
|
+
export type QueryManagerActions<
|
|
61
|
+
T extends QueryManagerItem,
|
|
62
|
+
ExtraArgs = any,
|
|
63
|
+
Meta extends QueryManagerMeta = QueryManagerMeta
|
|
64
|
+
> = Record<
|
|
65
|
+
string, QueryManagerAction<T, ExtraArgs, Meta>
|
|
66
|
+
>
|
|
67
|
+
|
|
68
|
+
export type UseListEffect<T extends QueryManagerItem = any> = (
|
|
69
|
+
listQuery: {
|
|
70
|
+
query: UseInfiniteQueryResult<UseListSelector<T>, Error>
|
|
71
|
+
refreshQuery: (silent?: boolean) => void
|
|
72
|
+
cancelQuery: () => void
|
|
73
|
+
}
|
|
74
|
+
) => void
|
|
75
|
+
|
|
76
|
+
export type QueryManagerOptions<
|
|
77
|
+
T extends QueryManagerItem,
|
|
78
|
+
ExtraArgs = any,
|
|
79
|
+
Meta extends QueryManagerMeta = QueryManagerMeta,
|
|
80
|
+
Actions extends QueryManagerActions<T, ExtraArgs, Meta> = QueryManagerActions<T, ExtraArgs, Meta>
|
|
81
|
+
> = {
|
|
82
|
+
name: string
|
|
83
|
+
itemType: T
|
|
84
|
+
queryClient: ReturnType<typeof useQueryClient>
|
|
85
|
+
|
|
86
|
+
listItems?: (limit: number, offset: number, args?: ExtraArgs) => Promise<PaginationResponse<T>>
|
|
87
|
+
createItem?: (data: Partial<T>, args?: ExtraArgs) => Promise<T>
|
|
88
|
+
updateItem?: (data: Partial<T>, args?: ExtraArgs) => Promise<T>
|
|
89
|
+
deleteItem?: (data: T, args?: ExtraArgs) => Promise<T>
|
|
90
|
+
retrieveItem?: (id: T['id']) => Promise<T>
|
|
91
|
+
|
|
92
|
+
useListEffect?: UseListEffect<T>
|
|
93
|
+
|
|
94
|
+
limit?: number
|
|
95
|
+
creation?: CreateOptions<T>
|
|
96
|
+
update?: UpdateOptions<T>
|
|
97
|
+
deletion?: DeleteOptions<T>
|
|
98
|
+
generateId?: () => T['id']
|
|
99
|
+
actions?: Actions
|
|
100
|
+
keyExtractor?: (item: T) => string
|
|
101
|
+
initialMeta?: Meta
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type QueryManagerActionTrigger<
|
|
105
|
+
A extends QueryManagerAction<any, any, any>,
|
|
106
|
+
Args extends any[] = A extends QueryManagerAction<any, any, any, infer _Args> ? _Args : any[]
|
|
107
|
+
> = (...args: Args) => any
|
|
108
|
+
|
|
109
|
+
export type QueryManagerActionTriggers<
|
|
110
|
+
Actions extends QueryManagerActions<any, any, any>
|
|
111
|
+
> = {
|
|
112
|
+
[K in keyof Actions]: QueryManagerActionTrigger<Actions[K]>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type InfinitePaginationData<T> = InfiniteData<PaginationResponse<T>, PageParam>
|
|
116
|
+
|
|
117
|
+
export type UseManagerArgs<T extends QueryManagerItem, ExtraArgs = any> = {
|
|
118
|
+
filter?: ExtraArgs
|
|
119
|
+
limit?: number
|
|
120
|
+
offset?: number
|
|
121
|
+
|
|
122
|
+
creation?: CreateOptions<T>
|
|
123
|
+
update?: UpdateOptions<T>
|
|
124
|
+
deletion?: DeleteOptions<T>
|
|
125
|
+
|
|
126
|
+
listOptions?: Pick<ListOptions<T, ExtraArgs>, 'queryOptions'>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type QueryManagerItem = {
|
|
130
|
+
id: string | number
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type AppendToPaginationParams<TItem extends QueryManagerItem, Filters = any> = {
|
|
134
|
+
item: TItem|TItem[]
|
|
135
|
+
to?: CreateOptions<TItem>['appendTo']
|
|
136
|
+
refreshKey?: QueryKey
|
|
137
|
+
onListsWithFilters?: Filters
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type AppendToPaginationReturn<TItem = any> = InfiniteData<TItem>
|
|
141
|
+
|
|
142
|
+
export type AppendToPagination<TItem extends QueryManagerItem, ExtraArgs = any> = (params: AppendToPaginationParams<TItem, ExtraArgs>) => Promise<void>
|
|
143
|
+
|
|
144
|
+
export type MutationCtx<T extends QueryManagerItem> = null | {
|
|
145
|
+
previousData?: InfinitePaginationData<T>
|
|
146
|
+
addedId?: T['id']
|
|
147
|
+
previousItem?: T
|
|
148
|
+
optimisticItem?: T
|
|
149
|
+
prevItemPages?:Record<string, [number, number]>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const isInfiniteQueryData = <T>(data: any): data is InfinitePaginationData<T> => {
|
|
153
|
+
return !!data?.pages && !!data?.pageParams
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type QueryStateValue<T extends QueryManagerItem> = {
|
|
157
|
+
pagesById: Record<T['id'], [number, number]>
|
|
158
|
+
itemIndexes: Record<T['id'], number>
|
|
159
|
+
key: QueryKey
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type QueryStateSubscriber<T extends QueryManagerItem> = (data: QueryStateValue<T>) => void
|
|
163
|
+
|
|
164
|
+
export type FilterKeyOrder = string[]
|
|
165
|
+
|
|
166
|
+
export type GetItemOptions<T extends QueryManagerItem> = {
|
|
167
|
+
forceRefetch?: boolean
|
|
168
|
+
fetchOnNotFoud?: boolean
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type SettableOptions<O extends QueryManagerOptions<any, any, any, any>> = Partial<
|
|
172
|
+
Pick<
|
|
173
|
+
O,
|
|
174
|
+
'limit' |
|
|
175
|
+
'creation' |
|
|
176
|
+
'update' |
|
|
177
|
+
'deletion'
|
|
178
|
+
> & {
|
|
179
|
+
meta: O['initialMeta']
|
|
180
|
+
}
|
|
181
|
+
>
|
|
182
|
+
|
|
183
|
+
export type OptionChangeListener<O extends QueryManagerOptions<any, any, any, any>> = (
|
|
184
|
+
options: O,
|
|
185
|
+
meta: O['initialMeta'],
|
|
186
|
+
) => any
|
|
187
|
+
|
|
188
|
+
export type UseActionOptions<T extends QueryManagerAction<any, any, any>> = UseMutationOptions<
|
|
189
|
+
Awaited<ReturnType<T>>,
|
|
190
|
+
unknown,
|
|
191
|
+
Parameters<T>[1]
|
|
192
|
+
>
|
|
193
|
+
|
|
194
|
+
export type UseListSelector<T> = {
|
|
195
|
+
pageParams: PageParam[]
|
|
196
|
+
pages: PaginationResponse<T>[]
|
|
197
|
+
flatItems: T[]
|
|
198
|
+
}
|
|
199
|
+
|