@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.
Files changed (50) hide show
  1. package/dist/factors/createQueryManager.d.ts +17 -14
  2. package/dist/factors/createQueryManager.d.ts.map +1 -1
  3. package/dist/factors/createQueryManager.js +18 -19
  4. package/dist/factors/createQueryManager.js.map +1 -1
  5. package/dist/factors/createQueryOperations.js +3 -6
  6. package/dist/factors/createQueryOperations.js.map +1 -1
  7. package/dist/factors/index.js +2 -18
  8. package/dist/factors/index.js.map +1 -1
  9. package/dist/index.js +3 -19
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/Mutations.js +39 -50
  12. package/dist/lib/Mutations.js.map +1 -1
  13. package/dist/lib/QueryClientEnhanced/index.js +45 -51
  14. package/dist/lib/QueryClientEnhanced/index.js.map +1 -1
  15. package/dist/lib/QueryClientEnhanced/types.js +1 -2
  16. package/dist/lib/QueryKeys.js +80 -91
  17. package/dist/lib/QueryKeys.js.map +1 -1
  18. package/dist/lib/QueryManager.d.ts +26 -10
  19. package/dist/lib/QueryManager.d.ts.map +1 -1
  20. package/dist/lib/QueryManager.js +173 -121
  21. package/dist/lib/QueryManager.js.map +1 -1
  22. package/dist/lib/QueryOperations/index.d.ts +2 -2
  23. package/dist/lib/QueryOperations/index.d.ts.map +1 -1
  24. package/dist/lib/QueryOperations/index.js +33 -33
  25. package/dist/lib/QueryOperations/index.js.map +1 -1
  26. package/dist/lib/QueryOperations/types.js +1 -2
  27. package/dist/lib/index.js +5 -21
  28. package/dist/lib/index.js.map +1 -1
  29. package/dist/types/core.d.ts +17 -3
  30. package/dist/types/core.d.ts.map +1 -1
  31. package/dist/types/core.js +1 -2
  32. package/dist/types/create.js +1 -2
  33. package/dist/types/delete.js +1 -2
  34. package/dist/types/index.js +7 -23
  35. package/dist/types/index.js.map +1 -1
  36. package/dist/types/list.d.ts +6 -4
  37. package/dist/types/list.d.ts.map +1 -1
  38. package/dist/types/list.js +1 -2
  39. package/dist/types/retrieve.js +1 -2
  40. package/dist/types/update.js +1 -2
  41. package/dist/types/utility.js +1 -2
  42. package/dist/utils/index.js +1 -17
  43. package/dist/utils/index.js.map +1 -1
  44. package/dist/utils/misc.js +3 -7
  45. package/dist/utils/misc.js.map +1 -1
  46. package/package.json +6 -5
  47. package/src/factors/createQueryManager.ts +19 -16
  48. package/src/lib/QueryManager.ts +55 -15
  49. package/src/types/core.ts +22 -3
  50. package/src/types/list.ts +7 -5
@@ -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 data = await this.options?.listFn?.(listLimit, listOffset, filters as F)
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 TypeGuards.isArray(data) ? data : [] as ListPaginationResponse<T>
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: () => this.options.listFn!(this.options.listLimit ?? 10, initialOffset, filters as F),
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
- /** Constructor options for `QueryManager`. All `*Fn` fields are optional; omitting one disables the corresponding operation and its generated hooks. */
16
- export type QueryManagerOptions<T extends QueryItem, F> = {
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<ListPaginationResponse<T>>
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
  }