@codeleap/query 7.0.2 → 7.1.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/factors/createQueryManager.d.ts +17 -14
- package/dist/factors/createQueryManager.d.ts.map +1 -1
- package/dist/factors/createQueryManager.js +18 -19
- package/dist/factors/createQueryManager.js.map +1 -1
- package/dist/factors/createQueryOperations.js +3 -6
- package/dist/factors/createQueryOperations.js.map +1 -1
- package/dist/factors/index.js +2 -18
- package/dist/factors/index.js.map +1 -1
- package/dist/index.js +3 -19
- package/dist/index.js.map +1 -1
- package/dist/lib/Mutations.js +39 -50
- package/dist/lib/Mutations.js.map +1 -1
- package/dist/lib/QueryClientEnhanced/index.js +45 -51
- package/dist/lib/QueryClientEnhanced/index.js.map +1 -1
- package/dist/lib/QueryClientEnhanced/types.js +1 -2
- package/dist/lib/QueryKeys.js +80 -91
- package/dist/lib/QueryKeys.js.map +1 -1
- package/dist/lib/QueryManager.d.ts +26 -10
- package/dist/lib/QueryManager.d.ts.map +1 -1
- package/dist/lib/QueryManager.js +173 -121
- package/dist/lib/QueryManager.js.map +1 -1
- package/dist/lib/QueryOperations/index.d.ts +2 -2
- package/dist/lib/QueryOperations/index.d.ts.map +1 -1
- package/dist/lib/QueryOperations/index.js +33 -33
- package/dist/lib/QueryOperations/index.js.map +1 -1
- package/dist/lib/QueryOperations/types.js +1 -2
- package/dist/lib/index.js +5 -21
- package/dist/lib/index.js.map +1 -1
- package/dist/types/core.d.ts +17 -3
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +1 -2
- package/dist/types/create.js +1 -2
- package/dist/types/delete.js +1 -2
- package/dist/types/index.js +7 -23
- package/dist/types/index.js.map +1 -1
- package/dist/types/list.d.ts +6 -4
- package/dist/types/list.d.ts.map +1 -1
- package/dist/types/list.js +1 -2
- package/dist/types/retrieve.js +1 -2
- package/dist/types/update.js +1 -2
- package/dist/types/utility.js +1 -2
- package/dist/utils/index.js +1 -17
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/misc.js +3 -7
- package/dist/utils/misc.js.map +1 -1
- package/package.json +6 -5
- package/src/factors/createQueryManager.ts +19 -16
- package/src/lib/QueryManager.ts +55 -15
- package/src/types/core.ts +22 -3
- package/src/types/list.ts +7 -5
package/src/lib/QueryManager.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FetchInfiniteQueryOptions, FetchQueryOptions, InfiniteData, MutationFunctionContext, QueryClient, QueryKey, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
|
|
2
|
-
import { useCallback } from 'react'
|
|
2
|
+
import { useCallback, useMemo } from 'react'
|
|
3
3
|
import { createQueryKeys, QueryKeys } from './QueryKeys'
|
|
4
4
|
import { createMutations, Mutations } from './Mutations'
|
|
5
|
-
import { CreateMutationCtx, CreateMutationOptions, ListPaginationResponse, ListQueryOptions, PageParam, QueryItem, QueryManagerOptions, RetrieveQueryOptions, UpdateMutationCtx, UpdateMutationOptions, DeleteMutationCtx, DeleteMutationOptions } from '../types'
|
|
5
|
+
import { CreateMutationCtx, CreateMutationOptions, ListPaginationResponse, ListQueryOptions, ListResponseMeta, PageParam, QueryItem, QueryManagerOptions, RetrieveQueryOptions, UpdateMutationCtx, UpdateMutationOptions, DeleteMutationCtx, DeleteMutationOptions } from '../types'
|
|
6
6
|
import { generateTempId } from '../utils'
|
|
7
7
|
import { TypeGuards } from '@codeleap/types'
|
|
8
8
|
import { QueryClientEnhanced } from './QueryClientEnhanced'
|
|
@@ -11,21 +11,23 @@ import { QueryClientEnhanced } from './QueryClientEnhanced'
|
|
|
11
11
|
* Comprehensive query manager class that provides hooks and utilities for managing CRUD operations with React Query
|
|
12
12
|
* @template T - The query item type that extends QueryItem
|
|
13
13
|
* @template F - The filter type used for list queries
|
|
14
|
-
*
|
|
14
|
+
* @template M - Optional metadata shape returned alongside list items (e.g. `{ count: number }`). Defaults to `Record<string, unknown>`.
|
|
15
|
+
*
|
|
15
16
|
* @description
|
|
16
17
|
* QueryManager provides a complete solution for managing list and individual item queries with:
|
|
17
18
|
* - Infinite scroll pagination for lists
|
|
19
|
+
* - Support for list response metadata (e.g. total count) via envelope responses
|
|
18
20
|
* - Optimistic updates for create, update, and delete operations
|
|
19
21
|
* - Automatic cache synchronization between list and individual queries
|
|
20
22
|
* - Built-in error handling and rollback mechanisms
|
|
21
23
|
*/
|
|
22
|
-
export class QueryManager<T extends QueryItem, F> {
|
|
24
|
+
export class QueryManager<T extends QueryItem, F, M extends ListResponseMeta = ListResponseMeta> {
|
|
23
25
|
queryClient: QueryClient
|
|
24
26
|
/**
|
|
25
27
|
* Creates a new QueryManager instance
|
|
26
28
|
* @param options - Configuration options for the query manager
|
|
27
29
|
*/
|
|
28
|
-
constructor(private options: QueryManagerOptions<T, F>) {
|
|
30
|
+
constructor(private options: QueryManagerOptions<T, F, M>) {
|
|
29
31
|
this.queryClient = options.queryClient instanceof QueryClientEnhanced ? options.queryClient.client : options.queryClient
|
|
30
32
|
this.queryKeys = createQueryKeys<T, F>(options.name, this.queryClient)
|
|
31
33
|
this.mutations = createMutations<T, F>(this.queryKeys, this.queryClient, options.name)
|
|
@@ -62,17 +64,28 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
62
64
|
/**
|
|
63
65
|
* React hook for infinite scroll list queries with pagination
|
|
64
66
|
* @param options - Configuration options for the list query
|
|
65
|
-
* @returns Object containing items array, query key, and query object
|
|
66
|
-
*
|
|
67
|
+
* @returns Object containing items array, response metadata, query key, and query object
|
|
68
|
+
*
|
|
69
|
+
* @description
|
|
70
|
+
* When `listFn` returns a `ListPaginationEnvelope` (an object with `listItems` and extra fields),
|
|
71
|
+
* the extra fields are exposed as typed `meta` in the return value. When `listFn` returns a plain
|
|
72
|
+
* array, `meta` is `null`. Meta is stored in the query cache under a companion key, so it
|
|
73
|
+
* survives component remounts and is available even when React Query serves from cache.
|
|
74
|
+
*
|
|
67
75
|
* @example
|
|
68
76
|
* ```typescript
|
|
77
|
+
* // Plain array response (backward compatible)
|
|
69
78
|
* const { items, query } = queryManager.useList({
|
|
70
79
|
* filters: { status: 'active' },
|
|
71
80
|
* limit: 20
|
|
72
81
|
* })
|
|
82
|
+
*
|
|
83
|
+
* // Envelope response with metadata
|
|
84
|
+
* const { items, meta } = queryManager.useList({ filters })
|
|
85
|
+
* console.log(meta?.count) // total item count from the API
|
|
73
86
|
* ```
|
|
74
87
|
*/
|
|
75
|
-
useList(options: ListQueryOptions<T, F> = {}) {
|
|
88
|
+
useList(options: ListQueryOptions<T, F, M> = {}) {
|
|
76
89
|
const {
|
|
77
90
|
limit,
|
|
78
91
|
filters,
|
|
@@ -83,15 +96,19 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
83
96
|
|
|
84
97
|
const queryKey = this.queryKeys.useListKeyWithFilters(filters)
|
|
85
98
|
|
|
99
|
+
const metaQueryKey = useMemo(() => [...queryKey, '__meta'], [queryKey])
|
|
100
|
+
|
|
86
101
|
const onSelect = useCallback((data: InfiniteData<ListPaginationResponse<T>, PageParam>) => {
|
|
87
102
|
const pages = data?.pages ?? []
|
|
103
|
+
const meta = this.queryClient.getQueryData<M>(metaQueryKey) ?? null
|
|
88
104
|
|
|
89
105
|
return {
|
|
90
106
|
pageParams: data?.pageParams,
|
|
91
107
|
pages,
|
|
92
108
|
allItems: pages.flat(),
|
|
109
|
+
meta,
|
|
93
110
|
}
|
|
94
|
-
}, [])
|
|
111
|
+
}, [metaQueryKey])
|
|
95
112
|
|
|
96
113
|
const query = useInfiniteQuery({
|
|
97
114
|
queryKey,
|
|
@@ -99,9 +116,19 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
99
116
|
queryFn: async (query) => {
|
|
100
117
|
const listOffset = query?.pageParam ?? 0
|
|
101
118
|
|
|
102
|
-
const
|
|
119
|
+
const raw = await this.options?.listFn?.(listLimit, listOffset, filters as F)
|
|
120
|
+
|
|
121
|
+
if (TypeGuards.isArray(raw)) {
|
|
122
|
+
return raw as ListPaginationResponse<T>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (raw && typeof raw === 'object' && 'listItems' in raw) {
|
|
126
|
+
const { listItems, ...meta } = raw
|
|
127
|
+
this.queryClient.setQueryData(metaQueryKey, meta as unknown as M)
|
|
128
|
+
return TypeGuards.isArray(listItems) ? listItems : [] as ListPaginationResponse<T>
|
|
129
|
+
}
|
|
103
130
|
|
|
104
|
-
return
|
|
131
|
+
return [] as ListPaginationResponse<T>
|
|
105
132
|
},
|
|
106
133
|
|
|
107
134
|
initialPageParam: 0,
|
|
@@ -137,8 +164,10 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
137
164
|
|
|
138
165
|
return {
|
|
139
166
|
items,
|
|
167
|
+
meta: query.data?.meta ?? null,
|
|
140
168
|
queryKey,
|
|
141
169
|
query,
|
|
170
|
+
listLimit
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
173
|
|
|
@@ -498,10 +527,12 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
498
527
|
* @param options - Prefetch options compatible with React Query's infinite queries
|
|
499
528
|
* @param options.initialOffset - Starting offset for pagination (default: 0)
|
|
500
529
|
* @returns Promise that resolves when prefetch is complete
|
|
501
|
-
*
|
|
530
|
+
*
|
|
502
531
|
* @description
|
|
503
532
|
* Use this method to preload paginated list data that users are likely to need soon.
|
|
504
|
-
*
|
|
533
|
+
* Handles both plain array and envelope responses from `listFn`, but only caches the
|
|
534
|
+
* items — metadata is discarded during prefetch and populated on the first client-side fetch.
|
|
535
|
+
*
|
|
505
536
|
* @example
|
|
506
537
|
* ```typescript
|
|
507
538
|
* const handle = () => {
|
|
@@ -510,7 +541,6 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
510
541
|
* { staleTime: 5 * 60 * 1000 }
|
|
511
542
|
* )
|
|
512
543
|
* }
|
|
513
|
-
*
|
|
514
544
|
* ```
|
|
515
545
|
*/
|
|
516
546
|
prefetchList(
|
|
@@ -523,7 +553,17 @@ export class QueryManager<T extends QueryItem, F> {
|
|
|
523
553
|
...prefetchOptions as any,
|
|
524
554
|
initialPageParam: initialOffset,
|
|
525
555
|
queryKey: this.queryKeys.listKeyWithFilters(filters),
|
|
526
|
-
queryFn: () =>
|
|
556
|
+
queryFn: async () => {
|
|
557
|
+
const raw = await this.options.listFn!(this.options.listLimit ?? 10, initialOffset, filters as F)
|
|
558
|
+
|
|
559
|
+
if (TypeGuards.isArray(raw)) return raw
|
|
560
|
+
|
|
561
|
+
if (raw && typeof raw === 'object' && 'listItems' in raw) {
|
|
562
|
+
return TypeGuards.isArray(raw.listItems) ? raw.listItems : []
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return []
|
|
566
|
+
},
|
|
527
567
|
})
|
|
528
568
|
}
|
|
529
569
|
}
|
package/src/types/core.ts
CHANGED
|
@@ -12,13 +12,31 @@ export type PageParam = number
|
|
|
12
12
|
/** Raw page payload returned by `listFn` — a flat array of items for one page. */
|
|
13
13
|
export type ListPaginationResponse<T extends QueryItem> = T[]
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
export type
|
|
15
|
+
/** Extra metadata from a paginated API response (everything except the items array). */
|
|
16
|
+
export type ListResponseMeta = Record<string, unknown>
|
|
17
|
+
|
|
18
|
+
/** Envelope returned by `listFn` when it includes metadata beyond the items array. `listItems` holds the page items; all other fields are treated as metadata. */
|
|
19
|
+
export type ListPaginationEnvelope<T extends QueryItem, M extends ListResponseMeta = ListResponseMeta> = {
|
|
20
|
+
listItems: T[]
|
|
21
|
+
} & M
|
|
22
|
+
|
|
23
|
+
/** What `listFn` is allowed to return: a flat array (backward compat) or an envelope with metadata. */
|
|
24
|
+
export type ListFnResponse<T extends QueryItem, M extends ListResponseMeta = ListResponseMeta> =
|
|
25
|
+
| T[]
|
|
26
|
+
| ListPaginationEnvelope<T, M>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Constructor options for `QueryManager`. All `*Fn` fields are optional; omitting one disables the corresponding operation and its generated hooks.
|
|
30
|
+
* @template T - The query item type that extends QueryItem
|
|
31
|
+
* @template F - The filter type used for list queries
|
|
32
|
+
* @template M - Optional metadata shape returned alongside list items (e.g. `{ count: number }`). Defaults to `Record<string, unknown>`.
|
|
33
|
+
*/
|
|
34
|
+
export type QueryManagerOptions<T extends QueryItem, F, M extends ListResponseMeta = ListResponseMeta> = {
|
|
17
35
|
name: string
|
|
18
36
|
|
|
19
37
|
queryClient: ReturnType<typeof useQueryClient> | QueryClientEnhanced
|
|
20
38
|
|
|
21
|
-
listFn?: (limit: number, offset: number, filters: F) => Promise<
|
|
39
|
+
listFn?: (limit: number, offset: number, filters: F) => Promise<ListFnResponse<T, M>>
|
|
22
40
|
|
|
23
41
|
/** Page size sent to `listFn`. Defaults to 10 when omitted. */
|
|
24
42
|
listLimit?: number
|
|
@@ -28,6 +46,7 @@ export type QueryManagerOptions<T extends QueryItem, F> = {
|
|
|
28
46
|
pageParams: number[]
|
|
29
47
|
pages: ListPaginationResponse<T>[]
|
|
30
48
|
allItems: T[]
|
|
49
|
+
meta: M | null
|
|
31
50
|
}, Error>) => void
|
|
32
51
|
|
|
33
52
|
retrieveFn?: (id: T['id']) => Promise<T>
|
package/src/types/list.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { QueryKey, UseInfiniteQueryOptions } from '@tanstack/react-query'
|
|
2
|
-
import { ListPaginationResponse, PageParam, QueryItem } from './core'
|
|
2
|
+
import { ListPaginationResponse, ListResponseMeta, PageParam, QueryItem } from './core'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalised shape of the infinite-query cache entry.
|
|
6
6
|
* `allItems` is a convenience flat-merge of every page's items — use it for rendering; use `pages` when you need per-page boundaries.
|
|
7
|
+
* `meta` holds extra data from the most recent `listFn` response (e.g. total count), or `null` when `listFn` returns a plain array.
|
|
7
8
|
*/
|
|
8
|
-
export type ListSelector<T extends QueryItem> = {
|
|
9
|
+
export type ListSelector<T extends QueryItem, M extends ListResponseMeta = ListResponseMeta> = {
|
|
9
10
|
pageParams: PageParam[]
|
|
10
11
|
pages: ListPaginationResponse<T>[]
|
|
11
12
|
allItems: T[]
|
|
13
|
+
meta: M | null
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
type InfiniteQueryOptions<T extends QueryItem> = UseInfiniteQueryOptions<
|
|
16
|
+
type InfiniteQueryOptions<T extends QueryItem, M extends ListResponseMeta = ListResponseMeta> = UseInfiniteQueryOptions<
|
|
15
17
|
ListPaginationResponse<T>,
|
|
16
18
|
Error,
|
|
17
|
-
ListSelector<T>,
|
|
19
|
+
ListSelector<T, M>,
|
|
18
20
|
QueryKey,
|
|
19
21
|
PageParam
|
|
20
22
|
>
|
|
21
23
|
|
|
22
24
|
/** Options accepted by the generated `useList` hook. Extends React Query's infinite-query options; `limit` and `filters` are forwarded to `listFn`. */
|
|
23
|
-
export type ListQueryOptions<T extends QueryItem, F> = Partial<InfiniteQueryOptions<T>> & {
|
|
25
|
+
export type ListQueryOptions<T extends QueryItem, F, M extends ListResponseMeta = ListResponseMeta> = Partial<InfiniteQueryOptions<T, M>> & {
|
|
24
26
|
limit?: number
|
|
25
27
|
filters?: F
|
|
26
28
|
}
|