@ic-reactor/react 3.2.0 → 3.3.1

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 (48) hide show
  1. package/README.md +58 -4
  2. package/dist/createActorHooks.d.ts +1 -1
  3. package/dist/createActorHooks.d.ts.map +1 -1
  4. package/dist/createActorHooks.js +10 -25
  5. package/dist/createActorHooks.js.map +1 -1
  6. package/dist/createInfiniteQuery.d.ts.map +1 -1
  7. package/dist/createInfiniteQuery.js +2 -12
  8. package/dist/createInfiniteQuery.js.map +1 -1
  9. package/dist/createMutation.d.ts.map +1 -1
  10. package/dist/createMutation.js +39 -56
  11. package/dist/createMutation.js.map +1 -1
  12. package/dist/createQuery.d.ts.map +1 -1
  13. package/dist/createQuery.js +31 -39
  14. package/dist/createQuery.js.map +1 -1
  15. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  16. package/dist/createSuspenseInfiniteQuery.js +1 -14
  17. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  18. package/dist/createSuspenseQuery.d.ts.map +1 -1
  19. package/dist/createSuspenseQuery.js +30 -39
  20. package/dist/createSuspenseQuery.js.map +1 -1
  21. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  22. package/dist/hooks/useActorInfiniteQuery.js +4 -5
  23. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  24. package/dist/hooks/useActorMutation.d.ts +9 -2
  25. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  26. package/dist/hooks/useActorMutation.js +15 -15
  27. package/dist/hooks/useActorMutation.js.map +1 -1
  28. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  29. package/dist/hooks/useActorSuspenseInfiniteQuery.js +4 -5
  30. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  31. package/dist/types.d.ts +26 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/utils.d.ts +26 -0
  34. package/dist/utils.d.ts.map +1 -0
  35. package/dist/utils.js +41 -0
  36. package/dist/utils.js.map +1 -0
  37. package/package.json +9 -9
  38. package/src/createActorHooks.ts +32 -31
  39. package/src/createInfiniteQuery.ts +2 -19
  40. package/src/createMutation.ts +52 -69
  41. package/src/createQuery.ts +41 -52
  42. package/src/createSuspenseInfiniteQuery.ts +1 -22
  43. package/src/createSuspenseQuery.ts +42 -56
  44. package/src/hooks/useActorInfiniteQuery.ts +4 -9
  45. package/src/hooks/useActorMutation.ts +40 -14
  46. package/src/hooks/useActorSuspenseInfiniteQuery.ts +4 -9
  47. package/src/types.ts +32 -0
  48. package/src/utils.ts +52 -0
@@ -33,18 +33,17 @@ import {
33
33
  UseMutationResult,
34
34
  InfiniteData,
35
35
  } from "@tanstack/react-query"
36
- import { createQuery } from "./createQuery"
37
- import { createSuspenseQuery } from "./createSuspenseQuery"
38
- import { createInfiniteQuery, InfiniteQueryConfig } from "./createInfiniteQuery"
39
- import {
40
- createSuspenseInfiniteQuery,
41
- SuspenseInfiniteQueryConfig,
42
- } from "./createSuspenseInfiniteQuery"
43
- import { createMutation } from "./createMutation"
36
+ import { useActorQuery } from "./hooks/useActorQuery"
37
+ import { useActorSuspenseQuery } from "./hooks/useActorSuspenseQuery"
38
+ import { useActorInfiniteQuery } from "./hooks/useActorInfiniteQuery"
39
+ import { useActorSuspenseInfiniteQuery } from "./hooks/useActorSuspenseInfiniteQuery"
40
+ import { useActorMutation } from "./hooks/useActorMutation"
44
41
  import {
45
42
  useActorMethod,
46
43
  UseActorMethodParameters,
47
44
  } from "./hooks/useActorMethod"
45
+ import { InfiniteQueryConfig } from "./createInfiniteQuery"
46
+ import { SuspenseInfiniteQueryConfig } from "./createSuspenseInfiniteQuery"
48
47
  import { QueryConfig, SuspenseQueryConfig, MutationConfig } from "./types"
49
48
 
50
49
  export type ActorHooks<A, T extends TransformKey> = {
@@ -108,33 +107,35 @@ export function createActorHooks<A, T extends TransformKey>(
108
107
  reactor: Reactor<A, T>
109
108
  ): ActorHooks<A, T> {
110
109
  return {
111
- useActorQuery: ((config: any) => {
112
- const { select, ...options } = config
113
- return createQuery(reactor, config).useQuery(options)
114
- }) as ActorHooks<A, T>["useActorQuery"],
110
+ useActorQuery: ((config: any) =>
111
+ useActorQuery({ ...config, reactor })) as ActorHooks<
112
+ A,
113
+ T
114
+ >["useActorQuery"],
115
115
 
116
- useActorSuspenseQuery: ((config: any) => {
117
- const { select, ...options } = config
118
- return createSuspenseQuery(reactor, config).useSuspenseQuery(options)
119
- }) as ActorHooks<A, T>["useActorSuspenseQuery"],
116
+ useActorSuspenseQuery: ((config: any) =>
117
+ useActorSuspenseQuery({ ...config, reactor })) as ActorHooks<
118
+ A,
119
+ T
120
+ >["useActorSuspenseQuery"],
120
121
 
121
- useActorInfiniteQuery: ((config) => {
122
- const { select, ...options } = config
123
- return createInfiniteQuery(reactor, config).useInfiniteQuery(options)
124
- }) as ActorHooks<A, T>["useActorInfiniteQuery"],
122
+ useActorInfiniteQuery: ((config: any) =>
123
+ useActorInfiniteQuery({ ...config, reactor })) as ActorHooks<
124
+ A,
125
+ T
126
+ >["useActorInfiniteQuery"],
125
127
 
126
- useActorSuspenseInfiniteQuery: ((config) => {
127
- const { select, ...options } = config
128
- return createSuspenseInfiniteQuery(
129
- reactor,
130
- config
131
- ).useSuspenseInfiniteQuery(options)
132
- }) as ActorHooks<A, T>["useActorSuspenseInfiniteQuery"],
128
+ useActorSuspenseInfiniteQuery: ((config: any) =>
129
+ useActorSuspenseInfiniteQuery({ ...config, reactor })) as ActorHooks<
130
+ A,
131
+ T
132
+ >["useActorSuspenseInfiniteQuery"],
133
133
 
134
- useActorMutation: ((config) => {
135
- const { onSuccess, invalidateQueries, ...options } = config
136
- return createMutation(reactor, config).useMutation(options)
137
- }) as ActorHooks<A, T>["useActorMutation"],
134
+ useActorMutation: ((config: any) =>
135
+ useActorMutation({ ...config, reactor })) as ActorHooks<
136
+ A,
137
+ T
138
+ >["useActorMutation"],
138
139
 
139
140
  useActorMethod: (config) =>
140
141
  useActorMethod({ ...config, reactor } as UseActorMethodParameters<
@@ -43,8 +43,7 @@ import {
43
43
  } from "@tanstack/react-query"
44
44
  import { CallConfig } from "@icp-sdk/core/agent"
45
45
  import { NoInfer } from "./types"
46
-
47
- const FACTORY_KEY_ARGS_QUERY_KEY = "__ic_reactor_factory_key_args"
46
+ import { mergeFactoryQueryKey } from "./utils"
48
47
 
49
48
  type InfiniteQueryFactoryFn<
50
49
  A,
@@ -63,22 +62,6 @@ type InfiniteQueryFactoryFn<
63
62
  >
64
63
  }
65
64
 
66
- const mergeFactoryQueryKey = (
67
- baseQueryKey?: QueryKey,
68
- keyArgs?: unknown
69
- ): QueryKey | undefined => {
70
- const merged: unknown[] = []
71
-
72
- if (baseQueryKey) {
73
- merged.push(...baseQueryKey)
74
- }
75
- if (keyArgs !== undefined) {
76
- merged.push({ [FACTORY_KEY_ARGS_QUERY_KEY]: keyArgs })
77
- }
78
-
79
- return merged.length > 0 ? merged : undefined
80
- }
81
-
82
65
  // ============================================================================
83
66
  // Type Definitions
84
67
  // ============================================================================
@@ -510,7 +493,7 @@ export function createInfiniteQueryFactory<
510
493
  ) => {
511
494
  const initialArgs = getArgs(config.initialPageParam)
512
495
  const keyArgs = config.getKeyArgs?.(initialArgs) ?? initialArgs
513
- const queryKey = mergeFactoryQueryKey(config.queryKey, keyArgs)
496
+ const queryKey = mergeFactoryQueryKey(config.queryKey, undefined, keyArgs)
514
497
 
515
498
  return createInfiniteQueryImpl<A, M, T, TPageParam, TSelected>(reactor, {
516
499
  ...(({ getKeyArgs: _getKeyArgs, ...rest }) => rest)(
@@ -30,6 +30,22 @@ import type {
30
30
  NoInfer,
31
31
  } from "./types"
32
32
 
33
+ // ============================================================================
34
+ // Internal helpers
35
+ // ============================================================================
36
+
37
+ /** Invalidate a list of query keys in parallel, filtering out undefineds. */
38
+ async function invalidateAll(
39
+ queryClient: Reactor<any, any>["queryClient"],
40
+ keys: (import("@tanstack/react-query").QueryKey | undefined)[]
41
+ ): Promise<void> {
42
+ await Promise.all(
43
+ keys.map((queryKey) =>
44
+ queryKey ? queryClient.invalidateQueries({ queryKey }) : Promise.resolve()
45
+ )
46
+ )
47
+ }
48
+
33
49
  // ============================================================================
34
50
  // Internal Implementation
35
51
  // ============================================================================
@@ -52,113 +68,80 @@ const createMutationImpl = <
52
68
  ...factoryOptions
53
69
  } = config
54
70
 
55
- // Direct execution function
71
+ /**
72
+ * Raw call without any invalidation logic.
73
+ * Used as mutationFn so that onSuccess handles all post-mutation work
74
+ * and there is no double-invalidation.
75
+ */
76
+ const callFn = (
77
+ args: ReactorArgs<A, M, T>
78
+ ): Promise<ReactorReturnOk<A, M, T>> =>
79
+ reactor.callMethod({ functionName, args, callConfig })
80
+
81
+ /**
82
+ * Imperative execution for non-React usage.
83
+ * Calls the canister method and invalidates factory-level queries.
84
+ * Use this in route loaders, scripts, or server-side code.
85
+ */
56
86
  const execute = async (
57
87
  args: ReactorArgs<A, M, T>
58
88
  ): Promise<ReactorReturnOk<A, M, T>> => {
59
- const result = await reactor.callMethod({
60
- functionName,
61
- args,
62
- callConfig,
63
- })
64
-
89
+ const result = await callFn(args)
65
90
  if (factoryInvalidateQueries) {
66
- await Promise.all(
67
- factoryInvalidateQueries.map((queryKey) => {
68
- return reactor.queryClient.invalidateQueries({ queryKey })
69
- })
70
- )
91
+ await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
71
92
  }
72
-
73
93
  return result
74
94
  }
75
95
 
76
96
  // Hook implementation
77
97
  const useMutationHook = (options?: MutationHookOptions<A, M, T>) => {
78
- const baseOptions = reactor.getQueryOptions({
79
- functionName,
80
- })
81
- // Extract our custom options
98
+ const baseOptions = reactor.getQueryOptions({ functionName })
82
99
  const {
83
100
  invalidateQueries: hookInvalidateQueries,
84
101
  onCanisterError: hookOnCanisterError,
85
102
  ...restOptions
86
- } = options || {}
103
+ } = options ?? {}
87
104
 
88
105
  return useMutation(
89
106
  {
90
107
  mutationKey: baseOptions.queryKey,
91
108
  ...factoryOptions,
92
109
  ...restOptions,
93
- mutationFn: execute,
110
+ // Use callFn (not execute) to avoid double-invalidation:
111
+ // factoryInvalidateQueries are handled in onSuccess below.
112
+ mutationFn: callFn,
94
113
  onSuccess: async (...args) => {
95
- // 1. Handle factory-level invalidateQueries
114
+ // 1. Factory-level invalidation
96
115
  if (factoryInvalidateQueries) {
97
- await Promise.all(
98
- factoryInvalidateQueries.map((queryKey) => {
99
- return reactor.queryClient.invalidateQueries({ queryKey })
100
- })
101
- )
116
+ await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
102
117
  }
103
-
104
- // 2. Handle hook-level invalidateQueries
118
+ // 2. Hook-level invalidation
105
119
  if (hookInvalidateQueries) {
106
- await Promise.all(
107
- hookInvalidateQueries.map((queryKey) => {
108
- if (queryKey) {
109
- return reactor.queryClient.invalidateQueries({ queryKey })
110
- }
111
- return Promise.resolve()
112
- })
113
- )
114
- }
115
-
116
- // 3. Call factory onSuccess
117
- if (factoryOnSuccess) {
118
- await factoryOnSuccess(...args)
119
- }
120
-
121
- // 4. Call hook-local onSuccess
122
- if (restOptions?.onSuccess) {
123
- await restOptions.onSuccess(...args)
120
+ await invalidateAll(reactor.queryClient, hookInvalidateQueries)
124
121
  }
122
+ // 3. Factory onSuccess
123
+ await factoryOnSuccess?.(...args)
124
+ // 4. Hook onSuccess
125
+ await restOptions.onSuccess?.(...args)
125
126
  },
126
127
  onError: (error, variables, context, mutation) => {
127
- // Check if this is a CanisterError (from Result { Err: E })
128
128
  if (isCanisterError(error)) {
129
- // 1. Call factory-level onCanisterError
130
- if (factoryOnCanisterError) {
131
- factoryOnCanisterError(error, variables)
132
- }
133
- // 2. Call hook-level onCanisterError
134
- if (hookOnCanisterError) {
135
- hookOnCanisterError(error, variables)
136
- }
137
- }
138
-
139
- // 3. Call factory-level onError (for all errors)
140
- if (factoryOnError) {
141
- factoryOnError(error, variables, context, mutation)
142
- }
143
-
144
- // 4. Call hook-level onError (for all errors)
145
- if (restOptions?.onError) {
146
- restOptions.onError(error, variables, context, mutation)
129
+ factoryOnCanisterError?.(error, variables)
130
+ hookOnCanisterError?.(error, variables)
147
131
  }
132
+ factoryOnError?.(error, variables, context, mutation)
133
+ restOptions.onError?.(error, variables, context, mutation)
148
134
  },
149
135
  },
150
136
  reactor.queryClient
151
137
  )
152
138
  }
153
139
 
154
- return {
155
- useMutation: useMutationHook,
156
- execute,
157
- }
140
+ return { useMutation: useMutationHook, execute }
158
141
  }
159
142
 
160
143
  // ============================================================================
161
- // Factory Function
144
+ // Public Factory Function
162
145
  // ============================================================================
163
146
 
164
147
  export function createMutation<
@@ -30,6 +30,7 @@ import type {
30
30
  QueryFactoryConfig,
31
31
  NoInfer,
32
32
  } from "./types"
33
+ import { buildChainedSelect } from "./utils"
33
34
 
34
35
  // ============================================================================
35
36
  // Internal Implementation
@@ -56,37 +57,34 @@ const createQueryImpl = <
56
57
  ...rest
57
58
  } = config
58
59
 
59
- const params = {
60
- functionName,
61
- args,
62
- queryKey: customQueryKey,
63
- }
60
+ const params = { functionName, args, queryKey: customQueryKey }
64
61
 
65
- // Get query key from actor manager
66
- const getQueryKey = (): QueryKey => {
67
- return reactor.generateQueryKey(params)
68
- }
62
+ const getQueryKey = (): QueryKey => reactor.generateQueryKey(params)
69
63
 
70
- // Fetch function for loaders (cache-first)
64
+ // Apply config.select to raw data (shared by fetch, getCacheData, and the hook)
65
+ const applySelect = (raw: TData): TSelected =>
66
+ select ? select(raw) : (raw as unknown as TSelected)
67
+
68
+ /** Cache-first fetch for use in loaders / route preloading. */
71
69
  const fetch = async (): Promise<TSelected> => {
72
70
  const result = await reactor.fetchQuery(params)
73
- return select ? select(result) : (result as TSelected)
71
+ return applySelect(result)
72
+ }
73
+
74
+ /** Fire-and-forget prefetch — warms the cache without blocking. */
75
+ const prefetch = (): Promise<void> => {
76
+ const baseOptions = reactor.getQueryOptions(params)
77
+ return reactor.queryClient.prefetchQuery({
78
+ queryKey: baseOptions.queryKey,
79
+ queryFn: baseOptions.queryFn,
80
+ staleTime,
81
+ })
74
82
  }
75
83
 
76
- // Implementation
77
84
  const useQueryHook: UseQueryWithSelect<TData, TSelected, TError> = (
78
85
  options: any
79
86
  ): any => {
80
87
  const baseOptions = reactor.getQueryOptions(params)
81
-
82
- // Chain the selects: raw -> config.select -> options.select
83
- const chainedSelect = (rawData: TData) => {
84
- const firstPass = config.select ? config.select(rawData) : rawData
85
- if (options?.select) {
86
- return options.select(firstPass)
87
- }
88
- return firstPass
89
- }
90
88
  return useQuery(
91
89
  {
92
90
  queryKey: baseOptions.queryKey,
@@ -94,50 +92,46 @@ const createQueryImpl = <
94
92
  ...rest,
95
93
  ...options,
96
94
  queryFn: baseOptions.queryFn,
97
- select: chainedSelect,
95
+ select: buildChainedSelect(select, options?.select),
98
96
  },
99
97
  reactor.queryClient
100
98
  )
101
99
  }
102
100
 
103
- // Invalidate function
104
101
  const invalidate = async (): Promise<void> => {
105
- const queryKey = getQueryKey()
106
- await reactor.queryClient.invalidateQueries({ queryKey })
102
+ await reactor.queryClient.invalidateQueries({ queryKey: getQueryKey() })
107
103
  }
108
104
 
109
- // Get data from cache without fetching
110
- const getCacheData: any = (selectFn?: (data: TData) => any) => {
111
- const cachedRawData = reactor.getQueryData(params)
112
-
113
- if (cachedRawData === undefined) {
114
- return undefined
115
- }
116
-
117
- // Apply config.select to raw cache data
118
- const selectedData = (
119
- config.select ? config.select(cachedRawData) : cachedRawData
120
- ) as TData
121
-
122
- // Apply optional select parameter
123
- if (selectFn) {
124
- return selectFn(selectedData)
125
- }
105
+ const getCacheData: QueryResult<TData, TSelected, TError>["getCacheData"] = (
106
+ selectFn?: (data: TSelected) => unknown
107
+ ): any => {
108
+ const raw = reactor.getQueryData(params)
109
+ if (raw === undefined) return undefined
110
+ const selected = applySelect(raw)
111
+ return selectFn ? selectFn(selected) : selected
112
+ }
126
113
 
127
- return selectedData
114
+ const setData: QueryResult<TData, TSelected, TError>["setData"] = (
115
+ updater
116
+ ) => {
117
+ return reactor.queryClient.setQueryData(getQueryKey(), updater as any) as
118
+ | TData
119
+ | undefined
128
120
  }
129
121
 
130
122
  return {
131
123
  fetch,
124
+ prefetch,
132
125
  useQuery: useQueryHook,
133
126
  invalidate,
134
127
  getQueryKey,
135
128
  getCacheData,
129
+ setData,
136
130
  }
137
131
  }
138
132
 
139
133
  // ============================================================================
140
- // Factory Function
134
+ // Public Factory Function
141
135
  // ============================================================================
142
136
 
143
137
  export function createQuery<
@@ -172,26 +166,21 @@ export function createQueryFactory<
172
166
  QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>>
173
167
  >()
174
168
 
175
- return (
176
- args: ReactorArgs<A, M, T>
177
- ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> => {
169
+ return (args: ReactorArgs<A, M, T>) => {
178
170
  const key = reactor.generateQueryKey({
179
171
  functionName: config.functionName as M,
180
172
  args,
181
173
  })
182
174
  const cacheKey = JSON.stringify(key)
183
175
 
184
- if (cache.has(cacheKey)) {
185
- return cache.get(cacheKey)!
186
- }
176
+ const existing = cache.get(cacheKey)
177
+ if (existing) return existing
187
178
 
188
179
  const result = createQueryImpl<A, M, T, TSelected>(reactor, {
189
180
  ...config,
190
181
  args,
191
182
  })
192
-
193
183
  cache.set(cacheKey, result)
194
-
195
184
  return result
196
185
  }
197
186
  }
@@ -45,8 +45,7 @@ import {
45
45
  } from "@tanstack/react-query"
46
46
  import { CallConfig } from "@icp-sdk/core/agent"
47
47
  import { NoInfer } from "./types"
48
-
49
- const FACTORY_KEY_ARGS_QUERY_KEY = "__ic_reactor_factory_key_args"
48
+ import { mergeFactoryQueryKey } from "./utils"
50
49
 
51
50
  type SuspenseInfiniteFactoryCallOptions = {
52
51
  queryKey?: QueryKey
@@ -78,26 +77,6 @@ type SuspenseInfiniteQueryFactoryFn<
78
77
  >
79
78
  }
80
79
 
81
- const mergeFactoryQueryKey = (
82
- baseQueryKey?: QueryKey,
83
- callQueryKey?: QueryKey,
84
- keyArgs?: unknown
85
- ): QueryKey | undefined => {
86
- const merged: unknown[] = []
87
-
88
- if (baseQueryKey) {
89
- merged.push(...baseQueryKey)
90
- }
91
- if (callQueryKey) {
92
- merged.push(...callQueryKey)
93
- }
94
- if (keyArgs !== undefined) {
95
- merged.push({ [FACTORY_KEY_ARGS_QUERY_KEY]: keyArgs })
96
- }
97
-
98
- return merged.length > 0 ? merged : undefined
99
- }
100
-
101
80
  // ============================================================================
102
81
  // Type Definitions
103
82
  // ============================================================================
@@ -35,6 +35,7 @@ import type {
35
35
  SuspenseQueryFactoryConfig,
36
36
  NoInfer,
37
37
  } from "./types"
38
+ import { buildChainedSelect } from "./utils"
38
39
 
39
40
  // ============================================================================
40
41
  // Internal Implementation
@@ -65,39 +66,35 @@ const createSuspenseQueryImpl = <
65
66
  ...rest
66
67
  } = config
67
68
 
68
- const params = {
69
- functionName,
70
- args,
71
- queryKey: customQueryKey,
72
- }
69
+ const params = { functionName, args, queryKey: customQueryKey }
73
70
 
74
- // Get query key from actor manager
75
- const getQueryKey = () => {
76
- return reactor.generateQueryKey(params)
77
- }
71
+ const getQueryKey = () => reactor.generateQueryKey(params)
72
+
73
+ const applySelect = (raw: TData): TSelected =>
74
+ select ? select(raw) : (raw as unknown as TSelected)
78
75
 
79
- // Fetch function for loaders (cache-first)
76
+ /** Cache-first fetch for use in loaders / route preloading. */
80
77
  const fetch = async (): Promise<TSelected> => {
81
78
  const result = await reactor.fetchQuery(params)
82
- return select ? select(result) : (result as TSelected)
79
+ return applySelect(result)
80
+ }
81
+
82
+ /** Fire-and-forget prefetch — warms the cache without blocking. */
83
+ const prefetch = (): Promise<void> => {
84
+ const baseOptions = reactor.getQueryOptions(params)
85
+ return reactor.queryClient.prefetchQuery({
86
+ queryKey: baseOptions.queryKey,
87
+ queryFn: baseOptions.queryFn,
88
+ staleTime,
89
+ })
83
90
  }
84
91
 
85
- // Implementation
86
92
  const useSuspenseQueryHook: UseSuspenseQueryWithSelect<
87
93
  TData,
88
94
  TSelected,
89
95
  TError
90
96
  > = (options: any): any => {
91
97
  const baseOptions = reactor.getQueryOptions(params)
92
-
93
- // Chain the selects: raw -> config.select -> options.select
94
- const chainedSelect = (rawData: TData) => {
95
- const firstPass = config.select ? config.select(rawData) : rawData
96
- if (options?.select) {
97
- return options.select(firstPass)
98
- }
99
- return firstPass
100
- }
101
98
  return useSuspenseQuery(
102
99
  {
103
100
  queryKey: baseOptions.queryKey,
@@ -105,50 +102,48 @@ const createSuspenseQueryImpl = <
105
102
  ...rest,
106
103
  ...options,
107
104
  queryFn: baseOptions.queryFn,
108
- select: chainedSelect,
105
+ select: buildChainedSelect(select, options?.select),
109
106
  },
110
107
  reactor.queryClient
111
108
  )
112
109
  }
113
110
 
114
- // Invalidate function
115
111
  const invalidate = async (): Promise<void> => {
116
- const queryKey = getQueryKey()
117
- await reactor.queryClient.invalidateQueries({ queryKey })
112
+ await reactor.queryClient.invalidateQueries({ queryKey: getQueryKey() })
118
113
  }
119
114
 
120
- // Get data from cache without fetching
121
- const getCacheData: any = (selectFn?: (data: TData) => any) => {
122
- const cachedRawData = reactor.getQueryData(params)
123
-
124
- if (cachedRawData === undefined) {
125
- return undefined
126
- }
127
-
128
- // Apply config.select to raw cache data
129
- const selectedData = (
130
- config.select ? config.select(cachedRawData) : cachedRawData
131
- ) as TData
132
-
133
- // Apply optional select parameter
134
- if (selectFn) {
135
- return selectFn(selectedData)
136
- }
115
+ const getCacheData: SuspenseQueryResult<
116
+ TData,
117
+ TSelected,
118
+ TError
119
+ >["getCacheData"] = (selectFn?: (data: TSelected) => unknown): any => {
120
+ const raw = reactor.getQueryData(params)
121
+ if (raw === undefined) return undefined
122
+ const selected = applySelect(raw)
123
+ return selectFn ? selectFn(selected) : selected
124
+ }
137
125
 
138
- return selectedData
126
+ const setData: SuspenseQueryResult<TData, TSelected, TError>["setData"] = (
127
+ updater
128
+ ) => {
129
+ return reactor.queryClient.setQueryData(getQueryKey(), updater as any) as
130
+ | TData
131
+ | undefined
139
132
  }
140
133
 
141
134
  return {
142
135
  fetch,
136
+ prefetch,
143
137
  useSuspenseQuery: useSuspenseQueryHook,
144
138
  invalidate,
145
139
  getQueryKey,
146
140
  getCacheData,
141
+ setData,
147
142
  }
148
143
  }
149
144
 
150
145
  // ============================================================================
151
- // Factory Function
146
+ // Public Factory Function
152
147
  // ============================================================================
153
148
 
154
149
  export function createSuspenseQuery<
@@ -186,30 +181,21 @@ export function createSuspenseQueryFactory<
186
181
  SuspenseQueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>>
187
182
  >()
188
183
 
189
- return (
190
- args: ReactorArgs<A, M, T>
191
- ): SuspenseQueryResult<
192
- QueryFnData<A, M, T>,
193
- TSelected,
194
- QueryError<A, M, T>
195
- > => {
184
+ return (args: ReactorArgs<A, M, T>) => {
196
185
  const key = reactor.generateQueryKey({
197
186
  functionName: config.functionName as M,
198
187
  args,
199
188
  })
200
189
  const cacheKey = JSON.stringify(key)
201
190
 
202
- if (cache.has(cacheKey)) {
203
- return cache.get(cacheKey)!
204
- }
191
+ const existing = cache.get(cacheKey)
192
+ if (existing) return existing
205
193
 
206
194
  const result = createSuspenseQueryImpl<A, M, T, TSelected>(reactor, {
207
195
  ...(config as SuspenseQueryFactoryConfig<A, M, T, TSelected>),
208
196
  args,
209
197
  })
210
-
211
198
  cache.set(cacheKey, result)
212
-
213
199
  return result
214
200
  }
215
201
  }