@codeleap/query 6.2.3 → 6.8.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.
Files changed (62) hide show
  1. package/dist/factors/createQueryManager.d.ts +36 -0
  2. package/dist/factors/createQueryManager.d.ts.map +1 -0
  3. package/dist/factors/createQueryOperations.d.ts +35 -0
  4. package/dist/factors/createQueryOperations.d.ts.map +1 -0
  5. package/dist/factors/index.d.ts +3 -0
  6. package/dist/factors/index.d.ts.map +1 -0
  7. package/dist/index.d.ts +4 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/lib/Mutations.d.ts +103 -0
  10. package/dist/lib/Mutations.d.ts.map +1 -0
  11. package/dist/lib/QueryClientEnhanced/index.d.ts +41 -0
  12. package/dist/lib/QueryClientEnhanced/index.d.ts.map +1 -0
  13. package/dist/lib/QueryClientEnhanced/types.d.ts +51 -0
  14. package/dist/lib/QueryClientEnhanced/types.d.ts.map +1 -0
  15. package/dist/lib/QueryKeys.d.ts +173 -0
  16. package/dist/lib/QueryKeys.d.ts.map +1 -0
  17. package/dist/lib/QueryManager.d.ts +202 -0
  18. package/dist/lib/QueryManager.d.ts.map +1 -0
  19. package/dist/lib/QueryOperations/index.d.ts +228 -0
  20. package/dist/lib/QueryOperations/index.d.ts.map +1 -0
  21. package/dist/lib/QueryOperations/types.d.ts +42 -0
  22. package/dist/lib/QueryOperations/types.d.ts.map +1 -0
  23. package/dist/lib/index.d.ts +6 -0
  24. package/dist/lib/index.d.ts.map +1 -0
  25. package/dist/types/core.d.ts +29 -0
  26. package/dist/types/core.d.ts.map +1 -0
  27. package/dist/types/create.d.ts +21 -0
  28. package/dist/types/create.d.ts.map +1 -0
  29. package/dist/types/delete.d.ts +18 -0
  30. package/dist/types/delete.d.ts.map +1 -0
  31. package/dist/types/index.d.ts +8 -0
  32. package/dist/types/index.d.ts.map +1 -0
  33. package/dist/types/list.d.ts +19 -0
  34. package/dist/types/list.d.ts.map +1 -0
  35. package/dist/types/retrieve.d.ts +17 -0
  36. package/dist/types/retrieve.d.ts.map +1 -0
  37. package/dist/types/update.d.ts +13 -0
  38. package/dist/types/update.d.ts.map +1 -0
  39. package/dist/types/utility.d.ts +23 -0
  40. package/dist/types/utility.d.ts.map +1 -0
  41. package/dist/utils/index.d.ts +2 -0
  42. package/dist/utils/index.d.ts.map +1 -0
  43. package/dist/utils/misc.d.ts +5 -0
  44. package/dist/utils/misc.d.ts.map +1 -0
  45. package/package.json +23 -8
  46. package/src/lib/Mutations.ts +30 -44
  47. package/src/lib/QueryClientEnhanced/index.ts +32 -4
  48. package/src/lib/QueryClientEnhanced/types.ts +25 -0
  49. package/src/lib/QueryKeys.ts +4 -2
  50. package/src/lib/QueryManager.ts +37 -33
  51. package/src/lib/QueryOperations/index.ts +11 -3
  52. package/src/lib/QueryOperations/types.ts +4 -3
  53. package/src/tests/setup.ts +14 -0
  54. package/src/types/core.ts +8 -1
  55. package/src/types/create.ts +10 -1
  56. package/src/types/delete.ts +8 -1
  57. package/src/types/list.ts +5 -0
  58. package/src/types/retrieve.ts +9 -0
  59. package/src/types/update.ts +3 -0
  60. package/src/types/utility.ts +5 -0
  61. package/src/utils/misc.ts +2 -0
  62. package/package.json.bak +0 -27
@@ -0,0 +1,13 @@
1
+ import { UseMutationOptions } from '@tanstack/react-query';
2
+ import { QueryItem } from './core';
3
+ /** Rollback context captured before an optimistic update is applied. Both fields are `undefined` when the item was not in cache at mutation time. */
4
+ export type UpdateMutationCtx<T> = {
5
+ previousItem: T | undefined;
6
+ optimisticItem: T | undefined;
7
+ };
8
+ /** Options forwarded to React Query's `useMutation` for an update operation. `mutationKey` and `mutationFn` are managed internally. */
9
+ export type UpdateMutationOptions<T extends QueryItem, F> = Omit<UseMutationOptions<T, Error, Partial<T>, UpdateMutationCtx<T>>, 'mutationKey' | 'mutationFn'> & {
10
+ /** Apply the updated fields to the cache immediately and revert to `previousItem` on error. */
11
+ optimistic?: boolean;
12
+ };
13
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/types/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,qJAAqJ;AACrJ,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;IAC3B,cAAc,EAAE,CAAC,GAAG,SAAS,CAAA;CAC9B,CAAA;AAED,uIAAuI;AACvI,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,IAAI,IAAI,CAC9D,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAC9D,aAAa,GAAG,YAAY,CAC7B,GAAG;IACF,+FAA+F;IAC/F,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA"}
@@ -0,0 +1,23 @@
1
+ import { QueryItem } from './core';
2
+ import { QueryKey, useQueryClient } from '@tanstack/react-query';
3
+ /** Alias for the React Query client instance type — avoids importing `useQueryClient` at every call site. */
4
+ export type QueryClient = ReturnType<typeof useQueryClient>;
5
+ /** An entity that may carry a client-generated `tempId` while an optimistic create is in-flight. */
6
+ export type WithTempId<T extends QueryItem> = T & {
7
+ tempId?: QueryItem['id'];
8
+ };
9
+ /** Exact location of an item inside the paginated cache: which page it lives on and its index within that page. */
10
+ export type ItemPosition = {
11
+ pageIndex: number;
12
+ itemIndex: number;
13
+ };
14
+ /** Sparse map of every cached list that contained a deleted item, together with the position it occupied. Used to re-insert the item on rollback. */
15
+ export type RemovedItemMap = [QueryKey, ItemPosition][];
16
+ /** Standard DRF-style paginated envelope. Wrap your API response in this type when the backend uses `count / next / previous / results`. */
17
+ export type PaginationResponse<T extends QueryItem> = {
18
+ count: number;
19
+ next: string;
20
+ previous: string;
21
+ results: T[];
22
+ };
23
+ //# sourceMappingURL=utility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utility.d.ts","sourceRoot":"","sources":["../../src/types/utility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEhE,6GAA6G;AAC7G,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,oGAAoG;AACpG,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,IAAI,CAAC,GAAG;IAChD,MAAM,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;CACzB,CAAA;AAED,mHAAmH;AACnH,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qJAAqJ;AACrJ,MAAM,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAA;AAEvD,4IAA4I;AAC5I,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,SAAS,IAAI;IACpD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,CAAC,EAAE,CAAA;CACb,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './misc';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA"}
@@ -0,0 +1,5 @@
1
+ /** Prefix used on every optimistically-created item id. Check `id.startsWith(tempIdPrefix)` to distinguish placeholder items from server-assigned ids. */
2
+ export declare const tempIdPrefix = "temp-id";
3
+ /** Returns a unique placeholder id for optimistic creates. The format is `temp-id-<uuidv4>` and is guaranteed never to collide with server-assigned ids. */
4
+ export declare const generateTempId: () => string;
5
+ //# sourceMappingURL=misc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../src/utils/misc.ts"],"names":[],"mappings":"AAsCA,0JAA0J;AAC1J,eAAO,MAAM,YAAY,YAAY,CAAA;AAErC,4JAA4J;AAC5J,eAAO,MAAM,cAAc,cAE1B,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,20 @@
1
1
  {
2
2
  "name": "@codeleap/query",
3
- "version": "6.2.3",
3
+ "version": "6.8.0",
4
4
  "main": "src/index.ts",
5
+ "types": "dist/index.d.ts",
6
+ "exports": {
7
+ ".": {
8
+ "source": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
5
18
  "license": "UNLICENSED",
6
19
  "repository": {
7
20
  "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
@@ -9,19 +22,21 @@
9
22
  "directory": "packages/query"
10
23
  },
11
24
  "devDependencies": {
12
- "@codeleap/config": "6.2.3",
13
- "@codeleap/types": "6.2.3",
25
+ "@codeleap/config": "6.8.0",
26
+ "@codeleap/types": "6.8.0",
27
+ "@codeleap/utils": "6.8.0",
14
28
  "ts-node-dev": "1.1.8"
15
29
  },
16
30
  "scripts": {
17
- "build": "echo 'No build needed'"
31
+ "build": "tsc --build tsconfig.build.json",
32
+ "typecheck": "bun tsc --noEmit -p ./tsconfig.json"
18
33
  },
19
34
  "peerDependencies": {
20
- "@codeleap/types": "6.2.3",
21
- "typescript": "5.5.2",
22
- "@tanstack/react-query": "5.89.0"
35
+ "@codeleap/types": "6.8.0",
36
+ "typescript": "6.0.3",
37
+ "@tanstack/react-query": "5.100.9"
23
38
  },
24
39
  "dependencies": {
25
40
  "fast-deep-equal": "3.1.3"
26
41
  }
27
- }
42
+ }
@@ -1,4 +1,4 @@
1
- import { InfiniteData } from '@tanstack/query-core'
1
+ import { InfiniteData, Query, QueryKey } from '@tanstack/query-core'
2
2
  import { QueryKeys } from './QueryKeys'
3
3
  import { ItemPosition, ListPaginationResponse, PageParam, QueryClient, QueryItem, RemovedItemMap, WithTempId } from '../types'
4
4
  import deepEqual from 'fast-deep-equal'
@@ -40,6 +40,30 @@ export class Mutations<T extends QueryItem, F> {
40
40
  * mutations.addItem(newUser, removedItemMap)
41
41
  * ```
42
42
  */
43
+ private _addItemAtKey(newItem: T, position: 'start' | 'end', queryKey: QueryKey): void {
44
+ const currentData = this.queryClient.getQueryData<InfiniteData<ListPaginationResponse<T>, PageParam>>(queryKey)
45
+
46
+ if (!currentData) {
47
+ this.queryClient.setQueryData(queryKey, { pageParams: [0], pages: [[newItem]] })
48
+ return
49
+ }
50
+
51
+ const updatedPages = [...currentData.pages]
52
+
53
+ if (position === 'start') {
54
+ updatedPages.length > 0
55
+ ? updatedPages[0] = [newItem, ...updatedPages[0]]
56
+ : updatedPages.push([newItem])
57
+ } else {
58
+ const last = updatedPages.length - 1
59
+ updatedPages.length > 0
60
+ ? updatedPages[last] = [...updatedPages[last], newItem]
61
+ : updatedPages.push([newItem])
62
+ }
63
+
64
+ this.queryClient.setQueryData(queryKey, { ...currentData, pages: updatedPages })
65
+ }
66
+
43
67
  addItem(newItem: T, position: 'start' | 'end' | RemovedItemMap = 'start', listFilters?: F) {
44
68
  const isMultiQueryKeys = Array.isArray(position) && position?.length >= 1
45
69
 
@@ -64,55 +88,17 @@ export class Mutations<T extends QueryItem, F> {
64
88
  }
65
89
  }
66
90
 
67
- const newData = {
68
- ...currentData,
69
- pages: updatedPages
70
- }
71
-
72
- this.queryClient.setQueryData(queryKey, newData)
91
+ this.queryClient.setQueryData(queryKey, { ...currentData, pages: updatedPages })
73
92
  }
74
93
 
75
94
  return
76
95
  }
77
96
 
78
- const queryKey = this.queryKeys.listKeyWithFilters(listFilters)
79
-
80
- const currentData = this.queryClient.getQueryData<InfiniteData<ListPaginationResponse<T>, PageParam>>(queryKey)
81
-
82
- if (!currentData) {
83
- const newData = {
84
- pageParams: [0],
85
- pages: [[newItem]]
86
- }
87
-
88
- this.queryClient.setQueryData(queryKey, newData)
89
-
90
- return
91
- }
92
-
93
- const updatedPages = [...currentData.pages]
94
-
95
- if (position === 'start') {
96
- if (updatedPages.length > 0) {
97
- updatedPages[0] = [newItem, ...updatedPages[0]]
98
- } else {
99
- updatedPages.push([newItem])
100
- }
101
- } else if (position === 'end') {
102
- if (updatedPages.length > 0) {
103
- const lastPageIndex = updatedPages.length - 1
104
- updatedPages[lastPageIndex] = [...updatedPages[lastPageIndex], newItem]
105
- } else {
106
- updatedPages.push([newItem])
107
- }
108
- }
109
-
110
- const newData = {
111
- ...currentData,
112
- pages: updatedPages
113
- }
97
+ this._addItemAtKey(newItem, position as 'start' | 'end', this.queryKeys.listKeyWithFilters(listFilters))
98
+ }
114
99
 
115
- this.queryClient.setQueryData(queryKey, newData)
100
+ addItemToQuery(newItem: T, position: 'start' | 'end' = 'start', query: Query): void {
101
+ this._addItemAtKey(newItem, position, query.queryKey)
116
102
  }
117
103
 
118
104
  /**
@@ -2,9 +2,17 @@ import { waitFor } from '@codeleap/utils'
2
2
  import { QueryClient, QueryKey, Query, hashKey, QueryCacheNotifyEvent, matchQuery, QueryOptions } from '@tanstack/react-query'
3
3
  import { DynamicEnhancedQuery, EnhancedQuery, PollQueryOptions, PollingResult, QueryKeyBuilder } from './types'
4
4
 
5
+ /**
6
+ * Thin wrapper around React Query's `QueryClient` that adds async primitives for polling, event listening, and proxy-based query handles.
7
+ * Pass an instance of this class wherever `QueryClient | QueryClientEnhanced` is accepted to unlock the enhanced API.
8
+ */
5
9
  export class QueryClientEnhanced {
6
10
  constructor(public client: QueryClient) { }
7
11
 
12
+ /**
13
+ * Subscribes to cache events for a single query key.
14
+ * Returns the unsubscribe function, or `undefined` when the query is not yet in the cache.
15
+ */
8
16
  listenToQuery(key: QueryKey, callback: (e: QueryCacheNotifyEvent) => void) {
9
17
  const cache = this.client.getQueryCache()
10
18
 
@@ -26,6 +34,10 @@ export class QueryClientEnhanced {
26
34
  return removeListener
27
35
  }
28
36
 
37
+ /**
38
+ * Repeatedly refetches a query on a fixed `interval` until the `callback` returns `{ stop: true }`.
39
+ * Rejects immediately if the query key is not present in the cache when called.
40
+ */
29
41
  async pollQuery<T, R>(
30
42
  key: QueryKey,
31
43
  options: PollQueryOptions<T, R>,
@@ -42,7 +54,7 @@ export class QueryClientEnhanced {
42
54
  let count = 0
43
55
  let result: PollingResult<R> = {
44
56
  stop: false,
45
- data: initialData,
57
+ data: initialData as R,
46
58
  }
47
59
 
48
60
  while (!result?.stop) {
@@ -68,6 +80,10 @@ export class QueryClientEnhanced {
68
80
  return result?.data
69
81
  }
70
82
 
83
+ /**
84
+ * Returns an `EnhancedQuery<T>` Proxy for `key`.
85
+ * `getData` and `setData` work even when the query is not yet in the cache; all other members log a warning and return `undefined` when the query is absent.
86
+ */
71
87
  queryProxy<T>(key: QueryKey) {
72
88
  const getClient = () => this
73
89
 
@@ -116,11 +132,11 @@ export class QueryClientEnhanced {
116
132
  }
117
133
 
118
134
  case 'ensureData':
119
- return (options) => {
135
+ return (options: Partial<Parameters<typeof client.client.ensureQueryData<T>>[0]>) => {
120
136
  return client.client.ensureQueryData<T>({
121
137
  queryKey: key,
122
138
  ...options
123
- })
139
+ } as Parameters<typeof client.client.ensureQueryData<T>>[0])
124
140
  }
125
141
 
126
142
  case 'refresh':
@@ -145,6 +161,10 @@ export class QueryClientEnhanced {
145
161
  })
146
162
  }
147
163
 
164
+ /**
165
+ * Resolves with the refreshed `Query` object once it reaches a settled `idle` state with a `dataUpdatedAt` or `errorUpdatedAt` timestamp newer than the moment of the call.
166
+ * Rejects when the query is absent from the cache or when the fetch results in an error.
167
+ */
148
168
  waitForRefresh<T>(key: QueryKey) {
149
169
  const initialQuery = this.client.getQueryCache().find({ exact: true, queryKey: key })
150
170
 
@@ -175,13 +195,17 @@ export class QueryClientEnhanced {
175
195
  reject()
176
196
  }
177
197
 
178
- removeListener()
198
+ removeListener?.()
179
199
  }
180
200
  })
181
201
  })
182
202
 
183
203
  }
184
204
 
205
+ /**
206
+ * Registers a static query key and returns an `EnhancedQuery<Data>` proxy for it.
207
+ * When `options` are provided they are applied as query defaults and the query is pre-seeded in the cache — useful for initialising queries outside of a React component.
208
+ */
185
209
  queryKey<Data>(k: QueryKey, options?: QueryOptions<Data>) {
186
210
  if (options) {
187
211
  this.client.setQueryDefaults(k, options)
@@ -201,6 +225,10 @@ export class QueryClientEnhanced {
201
225
  return this.queryProxy<Data>(k)
202
226
  }
203
227
 
228
+ /**
229
+ * Returns a `DynamicEnhancedQuery` proxy whose every member is a function that accepts `BuilderArgs` to derive the final key before delegating.
230
+ * Use this when the query key depends on runtime parameters (e.g. `(id: string) => ['users', id]`).
231
+ */
204
232
  dynamicQueryKey<Data, BuilderArgs extends any[] = any[]>(k: QueryKeyBuilder<BuilderArgs>) {
205
233
  const getClient = () => this
206
234
 
@@ -5,34 +5,59 @@ import {
5
5
  EnsureQueryDataOptions,
6
6
  } from '@tanstack/react-query'
7
7
 
8
+ /** A function that accepts typed arguments and returns the `QueryKey` array for a given query. Used with `dynamicQueryKey` to create parameterised query proxies. */
8
9
  export type QueryKeyBuilder<Args extends any[] = any[]> = (...args: Args) => QueryKey
9
10
 
11
+ /** Value returned by a `PollingCallback` on each tick. Set `stop: true` to terminate the polling loop. */
10
12
  export type PollingResult<T> = {
11
13
  stop: boolean
12
14
  data: T
13
15
  }
14
16
 
17
+ /**
18
+ * Called after each poll tick.
19
+ * @param query — the refreshed query snapshot
20
+ * @param count — zero-based tick counter
21
+ * @param prev — result data from the previous tick, `undefined` on the first call
22
+ * Return `{ stop: true, data }` to end polling; `{ stop: false, data }` to continue.
23
+ */
15
24
  export type PollingCallback<T, R> = (query: Query<T>, count: number, prev?: R) => Promise<PollingResult<R>>
16
25
 
17
26
  export type PollQueryOptions<T, R> = {
27
+ /** Milliseconds to wait between each refetch. */
18
28
  interval: number
19
29
  callback: PollingCallback<T, R>
30
+ /** When `true`, fires the first refetch immediately instead of waiting one `interval`. Defaults to `false`. */
20
31
  leading?: boolean
32
+ /** Seed value passed as `prev` to the callback on the very first tick. */
21
33
  initialData?: R
22
34
  }
23
35
 
36
+ /**
37
+ * A Proxy-based handle to a specific query inside `QueryClientEnhanced`.
38
+ * Extends the raw React Query `Query` object with ergonomic async helpers.
39
+ * Obtain via `queryKey()` or `queryProxy()` — do not instantiate directly.
40
+ */
24
41
  export interface EnhancedQuery<T> extends Query<T> {
42
+ /** Resolves once the query transitions to a settled `idle` state newer than when the call was made. Rejects if the query errors. */
25
43
  waitForRefresh(): Promise<Query<T>>
44
+ /** Subscribe to every cache notification for this query. Returns the unsubscribe function. */
26
45
  listen(callback: (e: QueryCacheNotifyEvent) => void): () => void
46
+ /** Triggers a refetch and resolves with the fresh data once settled. */
27
47
  refresh(): Promise<T>
28
48
  poll<R>(
29
49
  options: PollQueryOptions<T, R>
30
50
  ): Promise<R>
51
+ /** Synchronous cache read — returns `undefined` when the query has never been fetched. */
31
52
  getData(): T
32
53
  ensureData(options?: Partial<EnsureQueryDataOptions<T, Error, T, QueryKey, never>>): Promise<T>
33
54
  key: QueryKey
34
55
  }
35
56
 
57
+ /**
58
+ * Parameterised variant of `EnhancedQuery` produced by `dynamicQueryKey`.
59
+ * Every method accepts `BuilderArgs` to resolve the key before delegating to the underlying `EnhancedQuery`.
60
+ */
36
61
  export type DynamicEnhancedQuery<T, BuilderArgs extends any[]> = {
37
62
  [P in keyof EnhancedQuery<T>]: (...args: BuilderArgs) => EnhancedQuery<T>[P]
38
63
  }
@@ -43,14 +43,16 @@ export class QueryKeys<T extends QueryItem, F> {
43
43
  * @returns Query key array with or without filters
44
44
  */
45
45
  listKeyWithFilters(filters?: F): QueryKey {
46
- const hasValidFilters = filters != null && (
46
+ const hasValidFilters = !TypeGuards.isNil(filters) && (
47
47
  typeof filters !== 'object'
48
48
  ? Boolean(filters)
49
- : Object.values(filters).some(value =>
49
+ : Object.values(filters ?? {}).some(value =>
50
50
  value != null && (typeof value !== 'string' || value.trim() !== '')
51
51
  )
52
52
  )
53
53
 
54
+
55
+
54
56
  return hasValidFilters ? [...this.keys.list, filters] : this.keys.list
55
57
  }
56
58
 
@@ -1,10 +1,11 @@
1
- import { FetchInfiniteQueryOptions, FetchQueryOptions, InfiniteData, MutationFunctionContext, QueryKey, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
1
+ import { FetchInfiniteQueryOptions, FetchQueryOptions, InfiniteData, MutationFunctionContext, QueryClient, QueryKey, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
2
2
  import { useCallback } from 'react'
3
3
  import { createQueryKeys, QueryKeys } from './QueryKeys'
4
4
  import { createMutations, Mutations } from './Mutations'
5
5
  import { CreateMutationCtx, CreateMutationOptions, ListPaginationResponse, ListQueryOptions, PageParam, QueryItem, QueryManagerOptions, RetrieveQueryOptions, UpdateMutationCtx, UpdateMutationOptions, DeleteMutationCtx, DeleteMutationOptions } from '../types'
6
6
  import { generateTempId } from '../utils'
7
7
  import { TypeGuards } from '@codeleap/types'
8
+ import { QueryClientEnhanced } from './QueryClientEnhanced'
8
9
 
9
10
  /**
10
11
  * Comprehensive query manager class that provides hooks and utilities for managing CRUD operations with React Query
@@ -19,13 +20,15 @@ import { TypeGuards } from '@codeleap/types'
19
20
  * - Built-in error handling and rollback mechanisms
20
21
  */
21
22
  export class QueryManager<T extends QueryItem, F> {
23
+ queryClient: QueryClient
22
24
  /**
23
25
  * Creates a new QueryManager instance
24
26
  * @param options - Configuration options for the query manager
25
27
  */
26
28
  constructor(private options: QueryManagerOptions<T, F>) {
27
- this.queryKeys = createQueryKeys<T, F>(options.name, options.queryClient)
28
- this.mutations = createMutations<T, F>(this.queryKeys, options.queryClient, options.name)
29
+ this.queryClient = options.queryClient instanceof QueryClientEnhanced ? options.queryClient.client : options.queryClient
30
+ this.queryKeys = createQueryKeys<T, F>(options.name, this.queryClient)
31
+ this.mutations = createMutations<T, F>(this.queryKeys, this.queryClient, options.name)
29
32
  }
30
33
 
31
34
  /**
@@ -96,7 +99,7 @@ export class QueryManager<T extends QueryItem, F> {
96
99
  queryFn: async (query) => {
97
100
  const listOffset = query?.pageParam ?? 0
98
101
 
99
- const data = await this.options?.listFn?.(listLimit, listOffset, filters)
102
+ const data = await this.options?.listFn?.(listLimit, listOffset, filters as F)
100
103
 
101
104
  return TypeGuards.isArray(data) ? data : [] as ListPaginationResponse<T>
102
105
  },
@@ -185,7 +188,7 @@ export class QueryManager<T extends QueryItem, F> {
185
188
  queryKey,
186
189
 
187
190
  queryFn: () => {
188
- return this.options.retrieveFn(id)
191
+ return this.options.retrieveFn!(id)
189
192
  },
190
193
 
191
194
  select: (data) => {
@@ -234,7 +237,7 @@ export class QueryManager<T extends QueryItem, F> {
234
237
  ...mutationOptions
235
238
  } = options
236
239
 
237
- const onMutate = useCallback(async (data: Partial<T>, context: MutationFunctionContext) => {
240
+ const onMutate = useCallback(async (data: Partial<T>, context: MutationFunctionContext): Promise<CreateMutationCtx | undefined> => {
238
241
  if (providedOnMutate) providedOnMutate?.(data, context)
239
242
 
240
243
  if (optimistic) {
@@ -252,16 +255,16 @@ export class QueryManager<T extends QueryItem, F> {
252
255
  }
253
256
  }, [providedOnMutate, optimistic, listFilters])
254
257
 
255
- const onError = useCallback((error: Error, variables: Partial<T>, onMutateResult: CreateMutationCtx, context: MutationFunctionContext) => {
256
- if (providedOnError) providedOnError?.(error, variables, onMutateResult, context)
258
+ const onError = useCallback((error: Error, variables: Partial<T>, onMutateResult: CreateMutationCtx | undefined, context: MutationFunctionContext) => {
259
+ if (providedOnError && onMutateResult) providedOnError?.(error, variables, onMutateResult, context)
257
260
 
258
261
  if (!TypeGuards.isNil(onMutateResult?.tempId)) {
259
262
  this.mutations.removeItem(onMutateResult?.tempId)
260
263
  }
261
264
  }, [providedOnError])
262
265
 
263
- const onSuccess = useCallback((data: T, variables: Partial<T>, onMutateResult: CreateMutationCtx, context: MutationFunctionContext) => {
264
- if (providedOnSuccess) providedOnSuccess?.(data, variables, onMutateResult, context)
266
+ const onSuccess = useCallback((data: T, variables: Partial<T>, onMutateResult: CreateMutationCtx | undefined, context: MutationFunctionContext) => {
267
+ if (providedOnSuccess) providedOnSuccess?.(data, variables, onMutateResult , context)
265
268
 
266
269
  if (TypeGuards.isNil(onMutateResult?.tempId)) {
267
270
  this.mutations.addItem(data, appendTo, listFilters)
@@ -270,13 +273,13 @@ export class QueryManager<T extends QueryItem, F> {
270
273
  }
271
274
  }, [providedOnSuccess, listFilters])
272
275
 
273
- const mutation = useMutation<T, Error, Partial<T>, CreateMutationCtx>({
276
+ const mutation = useMutation<T, Error, Partial<T>, CreateMutationCtx | undefined>({
274
277
  ...mutationOptions,
275
278
 
276
279
  mutationKey: this.queryKeys.keys.create,
277
280
 
278
281
  mutationFn: (data: Partial<T>) => {
279
- return this.options.createFn(data)
282
+ return this.options.createFn!(data)
280
283
  },
281
284
 
282
285
  onMutate,
@@ -320,11 +323,12 @@ export class QueryManager<T extends QueryItem, F> {
320
323
  ...mutationOptions
321
324
  } = options
322
325
 
323
- const onMutate = useCallback(async (data: Partial<T>, context: MutationFunctionContext) => {
326
+ const onMutate = useCallback(async (data: Partial<T>, context: MutationFunctionContext): Promise<UpdateMutationCtx<T> | undefined> => {
324
327
  if (providedOnMutate) providedOnMutate?.(data, context)
325
328
 
326
329
  if (optimistic) {
327
- const previousItem = this.queryKeys.getRetrieveData(data?.id)
330
+ if (data?.id == null) return
331
+ const previousItem = this.queryKeys.getRetrieveData(data.id)
328
332
 
329
333
  if (!previousItem) return
330
334
 
@@ -342,27 +346,27 @@ export class QueryManager<T extends QueryItem, F> {
342
346
  }
343
347
  }, [providedOnMutate, optimistic])
344
348
 
345
- const onError = useCallback((error: Error, variables: Partial<T>, onMutateResult: UpdateMutationCtx<T>, context: MutationFunctionContext) => {
346
- if (providedOnError) providedOnError?.(error, variables, onMutateResult, context)
349
+ const onError = useCallback((error: Error, variables: Partial<T>, onMutateResult: UpdateMutationCtx<T> | undefined, context: MutationFunctionContext) => {
350
+ if (providedOnError && onMutateResult) providedOnError?.(error, variables, onMutateResult, context)
347
351
 
348
352
  if (!TypeGuards.isNil(onMutateResult?.previousItem?.id)) {
349
353
  this.mutations.updateItems(onMutateResult?.previousItem)
350
354
  }
351
355
  }, [providedOnError])
352
356
 
353
- const onSuccess = useCallback((data: T, variables: Partial<T>, onMutateResult: UpdateMutationCtx<T>, context: MutationFunctionContext) => {
354
- if (providedOnSuccess) providedOnSuccess?.(data, variables, onMutateResult, context)
357
+ const onSuccess = useCallback((data: T, variables: Partial<T>, onMutateResult: UpdateMutationCtx<T> | undefined, context: MutationFunctionContext) => {
358
+ if (providedOnSuccess && onMutateResult) providedOnSuccess?.(data, variables, onMutateResult, context)
355
359
 
356
360
  this.mutations.updateItems(data)
357
361
  }, [providedOnSuccess])
358
362
 
359
- const mutation = useMutation<T, Error, Partial<T>, UpdateMutationCtx<T>>({
363
+ const mutation = useMutation<T, Error, Partial<T>, UpdateMutationCtx<T> | undefined>({
360
364
  ...mutationOptions,
361
365
 
362
366
  mutationKey: this.queryKeys.keys.update,
363
367
 
364
368
  mutationFn: (data: Partial<T>) => {
365
- return this.options.updateFn(data)
369
+ return this.options.updateFn!(data)
366
370
  },
367
371
 
368
372
  onMutate,
@@ -407,7 +411,7 @@ export class QueryManager<T extends QueryItem, F> {
407
411
  ...mutationOptions
408
412
  } = options
409
413
 
410
- const onMutate = useCallback(async (id: T['id'], context: MutationFunctionContext) => {
414
+ const onMutate = useCallback(async (id: T['id'], context: MutationFunctionContext): Promise<DeleteMutationCtx<T> | undefined> => {
411
415
  if (providedOnMutate) providedOnMutate?.(id, context)
412
416
 
413
417
  if (optimistic) {
@@ -424,32 +428,32 @@ export class QueryManager<T extends QueryItem, F> {
424
428
  }
425
429
  }, [providedOnMutate, optimistic])
426
430
 
427
- const onError = useCallback((error: Error, variables: T['id'], onMutateResult: DeleteMutationCtx<T>, context: MutationFunctionContext) => {
428
- if (providedOnError) providedOnError?.(error, variables, onMutateResult, context)
431
+ const onError = useCallback((error: Error, variables: T['id'], onMutateResult: DeleteMutationCtx<T> | undefined, context: MutationFunctionContext) => {
432
+ if (providedOnError && onMutateResult) providedOnError?.(error, variables, onMutateResult, context)
429
433
 
430
434
  if (!TypeGuards.isNil(onMutateResult?.previousItem?.id)) {
431
435
  this.mutations.addItem(
432
436
  onMutateResult?.previousItem,
433
- onMutateResult?.removedAt,
437
+ onMutateResult?.removedAt ?? undefined,
434
438
  )
435
439
  }
436
440
  }, [providedOnError])
437
441
 
438
- const onSuccess = useCallback((data: T['id'], variables: T['id'], onMutateResult: DeleteMutationCtx<T>, context: MutationFunctionContext) => {
439
- if (providedOnSuccess) providedOnSuccess?.(data, variables, onMutateResult, context)
442
+ const onSuccess = useCallback((data: unknown, variables: T['id'], onMutateResult: DeleteMutationCtx<T> | undefined, context: MutationFunctionContext) => {
443
+ if (providedOnSuccess && onMutateResult) providedOnSuccess?.(data as T['id'], variables, onMutateResult, context)
440
444
 
441
445
  if (TypeGuards.isNil(onMutateResult?.previousItem?.id)) {
442
- this.mutations.removeItem(data)
446
+ this.mutations.removeItem(data as T['id'])
443
447
  }
444
448
  }, [providedOnSuccess])
445
449
 
446
- const mutation = useMutation<unknown, Error, T['id'], DeleteMutationCtx<T>>({
450
+ const mutation = useMutation<unknown, Error, T['id'], DeleteMutationCtx<T> | undefined>({
447
451
  ...mutationOptions,
448
452
 
449
453
  mutationKey: this.queryKeys.keys.delete,
450
454
 
451
455
  mutationFn: async (id: QueryItem['id']) => {
452
- return this.options.deleteFn(id)
456
+ return this.options.deleteFn!(id)
453
457
  },
454
458
 
455
459
  onMutate,
@@ -481,10 +485,10 @@ export class QueryManager<T extends QueryItem, F> {
481
485
  * ```
482
486
  */
483
487
  prefetchRetrieve(id: T['id'], options: Omit<FetchQueryOptions<T, Error, T, QueryKey, never>, 'queryKey' | 'queryFn'> = {}) {
484
- return this.options.queryClient.prefetchQuery({
488
+ return this.queryClient.prefetchQuery({
485
489
  ...options,
486
490
  queryKey: this.queryKeys.keys.retrieve(id),
487
- queryFn: () => this.options.retrieveFn(id),
491
+ queryFn: () => this.options.retrieveFn!(id),
488
492
  })
489
493
  }
490
494
 
@@ -515,11 +519,11 @@ export class QueryManager<T extends QueryItem, F> {
515
519
  ) {
516
520
  const { initialOffset = 0, ...prefetchOptions } = options
517
521
 
518
- return this.options.queryClient.prefetchInfiniteQuery({
522
+ return this.queryClient.prefetchInfiniteQuery({
519
523
  ...prefetchOptions as any,
520
524
  initialPageParam: initialOffset,
521
525
  queryKey: this.queryKeys.listKeyWithFilters(filters),
522
- queryFn: () => this.options.listFn(this.options.listLimit ?? 10, initialOffset, filters),
526
+ queryFn: () => this.options.listFn!(this.options.listLimit ?? 10, initialOffset, filters as F),
523
527
  })
524
528
  }
525
529
  }
@@ -1,5 +1,6 @@
1
1
  import { useMutation, useQuery, UseQueryOptions, UseMutationOptions, QueryKey, FetchQueryOptions } from '@tanstack/react-query'
2
2
  import { QueryOperationsOptions, MutationFn, QueryFn, InferMutationParams, InferMutationReturn, InferQueryParams, InferQueryReturn } from './types'
3
+ import { QueryClientEnhanced } from '../QueryClientEnhanced'
3
4
 
4
5
  /**
5
6
  * Builder class for creating type-safe query and mutation operations
@@ -41,6 +42,13 @@ export class QueryOperations<
41
42
  return this._mutations
42
43
  }
43
44
 
45
+ get queryClient(){
46
+ if(this._options.queryClient instanceof QueryClientEnhanced) {
47
+ return this._options.queryClient.client
48
+ }
49
+ return this._options.queryClient
50
+ }
51
+
44
52
  /**
45
53
  * Gets all registered query functions
46
54
  * @returns Readonly record of query functions
@@ -297,7 +305,7 @@ export class QueryOperations<
297
305
 
298
306
  const queryFn = this._queries[queryKey] as QueryFn
299
307
 
300
- return this._options.queryClient.prefetchQuery<InferQueryReturn<TQueries[K]>, Error, T, QueryKey>({
308
+ return this.queryClient.prefetchQuery<InferQueryReturn<TQueries[K]>, Error, T, QueryKey>({
301
309
  queryKey: prefetchQueryKey,
302
310
  queryFn: queryFn,
303
311
  ...options,
@@ -340,12 +348,12 @@ export class QueryOperations<
340
348
  ) {
341
349
  const prefetchQueryKey = this.getQueryKey(queryKey, params)
342
350
 
343
- const cachedData = this._options.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
351
+ const cachedData = this.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
344
352
 
345
353
  if (!cachedData) {
346
354
  await this.prefetchQuery(queryKey, params, options)
347
355
  }
348
356
 
349
- return this._options.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
357
+ return this.queryClient.getQueryData<T, QueryKey>(prefetchQueryKey)
350
358
  }
351
359
  }