@ic-reactor/react 3.12.5 → 4.0.0-beta.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.
- package/README.md +236 -423
- package/dist/index.d.ts +259 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +470 -17
- package/dist/index.js.map +1 -1
- package/llms.txt +82 -54
- package/package.json +12 -28
- package/src/index.tsx +611 -0
- package/dist/auth/authentication-manager.d.ts +0 -123
- package/dist/auth/authentication-manager.d.ts.map +0 -1
- package/dist/auth/authentication-manager.js +0 -767
- package/dist/auth/authentication-manager.js.map +0 -1
- package/dist/auth/constants.d.ts +0 -24
- package/dist/auth/constants.d.ts.map +0 -1
- package/dist/auth/constants.js +0 -24
- package/dist/auth/constants.js.map +0 -1
- package/dist/auth/createIdentityAttributeHooks.d.ts +0 -14
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +0 -1
- package/dist/auth/createIdentityAttributeHooks.js +0 -106
- package/dist/auth/createIdentityAttributeHooks.js.map +0 -1
- package/dist/auth/identity-attributes-manager.d.ts +0 -26
- package/dist/auth/identity-attributes-manager.d.ts.map +0 -1
- package/dist/auth/identity-attributes-manager.js +0 -107
- package/dist/auth/identity-attributes-manager.js.map +0 -1
- package/dist/auth/identity-attributes.d.ts +0 -19
- package/dist/auth/identity-attributes.d.ts.map +0 -1
- package/dist/auth/identity-attributes.js +0 -170
- package/dist/auth/identity-attributes.js.map +0 -1
- package/dist/auth/index.d.ts +0 -8
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -8
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/local-ii-probe.d.ts +0 -46
- package/dist/auth/local-ii-probe.d.ts.map +0 -1
- package/dist/auth/local-ii-probe.js +0 -102
- package/dist/auth/local-ii-probe.js.map +0 -1
- package/dist/auth/types.d.ts +0 -179
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js +0 -2
- package/dist/auth/types.js.map +0 -1
- package/dist/createActorHooks.d.ts +0 -52
- package/dist/createActorHooks.d.ts.map +0 -1
- package/dist/createActorHooks.js +0 -17
- package/dist/createActorHooks.js.map +0 -1
- package/dist/createInfiniteQuery.d.ts +0 -144
- package/dist/createInfiniteQuery.d.ts.map +0 -1
- package/dist/createInfiniteQuery.js +0 -174
- package/dist/createInfiniteQuery.js.map +0 -1
- package/dist/createMutation.d.ts +0 -30
- package/dist/createMutation.d.ts.map +0 -1
- package/dist/createMutation.js +0 -192
- package/dist/createMutation.js.map +0 -1
- package/dist/createQuery.d.ts +0 -30
- package/dist/createQuery.d.ts.map +0 -1
- package/dist/createQuery.js +0 -117
- package/dist/createQuery.js.map +0 -1
- package/dist/createSuspenseInfiniteQuery.d.ts +0 -147
- package/dist/createSuspenseInfiniteQuery.d.ts.map +0 -1
- package/dist/createSuspenseInfiniteQuery.js +0 -177
- package/dist/createSuspenseInfiniteQuery.js.map +0 -1
- package/dist/createSuspenseQuery.d.ts +0 -25
- package/dist/createSuspenseQuery.d.ts.map +0 -1
- package/dist/createSuspenseQuery.js +0 -111
- package/dist/createSuspenseQuery.js.map +0 -1
- package/dist/defineReactor.d.ts +0 -125
- package/dist/defineReactor.d.ts.map +0 -1
- package/dist/defineReactor.js +0 -180
- package/dist/defineReactor.js.map +0 -1
- package/dist/hooks/createAuthHooks.d.ts +0 -43
- package/dist/hooks/createAuthHooks.d.ts.map +0 -1
- package/dist/hooks/createAuthHooks.js +0 -131
- package/dist/hooks/createAuthHooks.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -21
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -24
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts +0 -39
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +0 -1
- package/dist/hooks/useActorInfiniteQuery.js +0 -56
- package/dist/hooks/useActorInfiniteQuery.js.map +0 -1
- package/dist/hooks/useActorMethod.d.ts +0 -115
- package/dist/hooks/useActorMethod.d.ts.map +0 -1
- package/dist/hooks/useActorMethod.js +0 -250
- package/dist/hooks/useActorMethod.js.map +0 -1
- package/dist/hooks/useActorMutation.d.ts +0 -48
- package/dist/hooks/useActorMutation.d.ts.map +0 -1
- package/dist/hooks/useActorMutation.js +0 -69
- package/dist/hooks/useActorMutation.js.map +0 -1
- package/dist/hooks/useActorQuery.d.ts +0 -32
- package/dist/hooks/useActorQuery.d.ts.map +0 -1
- package/dist/hooks/useActorQuery.js +0 -46
- package/dist/hooks/useActorQuery.js.map +0 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +0 -39
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +0 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +0 -56
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +0 -1
- package/dist/hooks/useActorSuspenseQuery.d.ts +0 -32
- package/dist/hooks/useActorSuspenseQuery.d.ts.map +0 -1
- package/dist/hooks/useActorSuspenseQuery.js +0 -47
- package/dist/hooks/useActorSuspenseQuery.js.map +0 -1
- package/dist/types.d.ts +0 -270
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -51
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -105
- package/dist/utils.js.map +0 -1
- package/dist/validation.d.ts +0 -131
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -125
- package/dist/validation.js.map +0 -1
- package/src/auth/authentication-manager.ts +0 -860
- package/src/auth/constants.ts +0 -32
- package/src/auth/createIdentityAttributeHooks.ts +0 -143
- package/src/auth/identity-attributes-manager.ts +0 -131
- package/src/auth/identity-attributes.ts +0 -270
- package/src/auth/index.ts +0 -7
- package/src/auth/local-ii-probe.ts +0 -147
- package/src/auth/types.ts +0 -200
- package/src/createActorHooks.ts +0 -220
- package/src/createInfiniteQuery.ts +0 -578
- package/src/createMutation.ts +0 -341
- package/src/createQuery.ts +0 -237
- package/src/createSuspenseInfiniteQuery.ts +0 -601
- package/src/createSuspenseQuery.ts +0 -234
- package/src/defineReactor.ts +0 -324
- package/src/hooks/createAuthHooks.ts +0 -189
- package/src/hooks/index.ts +0 -103
- package/src/hooks/useActorInfiniteQuery.ts +0 -205
- package/src/hooks/useActorMethod.ts +0 -507
- package/src/hooks/useActorMutation.ts +0 -205
- package/src/hooks/useActorQuery.ts +0 -124
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +0 -205
- package/src/hooks/useActorSuspenseQuery.ts +0 -132
- package/src/index.ts +0 -23
- package/src/types.ts +0 -499
- package/src/utils.ts +0 -121
- package/src/validation.ts +0 -202
package/src/createMutation.ts
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mutation Factory - Generic wrapper for mutating canister data
|
|
3
|
-
*
|
|
4
|
-
* Creates unified mutation hooks for any canister method.
|
|
5
|
-
* Works with any Reactor instance.
|
|
6
|
-
* Use this when one mutation should support both React hooks and imperative
|
|
7
|
-
* execution outside React.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* const transferMutation = createMutation(reactor, {
|
|
11
|
-
* functionName: "transfer",
|
|
12
|
-
* onSuccess: () => console.log("Success!"),
|
|
13
|
-
* })
|
|
14
|
-
*
|
|
15
|
-
* // In component
|
|
16
|
-
* const { mutate, isPending } = transferMutation.useMutation()
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* const transferMutation = createMutation(reactor, {
|
|
20
|
-
* functionName: "transfer",
|
|
21
|
-
* onCanisterError: (err) => console.error(err.code),
|
|
22
|
-
* })
|
|
23
|
-
*
|
|
24
|
-
* // Outside React (loader/service/script)
|
|
25
|
-
* await transferMutation.execute([{ to: "aaaaa-aa", amount: "1000" }])
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
useMutation,
|
|
30
|
-
type MutationFunctionContext,
|
|
31
|
-
} from "@tanstack/react-query"
|
|
32
|
-
import type {
|
|
33
|
-
Reactor,
|
|
34
|
-
FunctionName,
|
|
35
|
-
ReactorArgs,
|
|
36
|
-
TransformKey,
|
|
37
|
-
ReactorReturnOk,
|
|
38
|
-
ReactorReturnErr,
|
|
39
|
-
} from "@ic-reactor/core"
|
|
40
|
-
import { isCanisterError } from "@ic-reactor/core"
|
|
41
|
-
import type {
|
|
42
|
-
MutationConfig,
|
|
43
|
-
MutationResult,
|
|
44
|
-
MutationHookOptions,
|
|
45
|
-
NoInfer,
|
|
46
|
-
} from "./types.js"
|
|
47
|
-
|
|
48
|
-
// ============================================================================
|
|
49
|
-
// Internal helpers
|
|
50
|
-
// ============================================================================
|
|
51
|
-
|
|
52
|
-
/** Invalidate a list of query keys in parallel, filtering out undefineds. */
|
|
53
|
-
async function invalidateAll(
|
|
54
|
-
queryClient: Reactor<any, any>["queryClient"],
|
|
55
|
-
keys: (import("@tanstack/react-query").QueryKey | undefined)[]
|
|
56
|
-
): Promise<void> {
|
|
57
|
-
await Promise.all(
|
|
58
|
-
keys.map((queryKey) =>
|
|
59
|
-
queryKey ? queryClient.invalidateQueries({ queryKey }) : Promise.resolve()
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Internal Implementation
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
const createMutationImpl = <
|
|
69
|
-
Service,
|
|
70
|
-
Method extends FunctionName<Service> = FunctionName<Service>,
|
|
71
|
-
Transform extends TransformKey = "candid",
|
|
72
|
-
TOnMutateResult = unknown,
|
|
73
|
-
>(
|
|
74
|
-
reactor: Reactor<Service, Transform>,
|
|
75
|
-
config: MutationConfig<
|
|
76
|
-
Service,
|
|
77
|
-
Method,
|
|
78
|
-
Transform,
|
|
79
|
-
TOnMutateResult | undefined
|
|
80
|
-
>
|
|
81
|
-
): MutationResult<Service, Method, Transform> => {
|
|
82
|
-
const {
|
|
83
|
-
functionName,
|
|
84
|
-
callConfig,
|
|
85
|
-
invalidateQueries: factoryInvalidateQueries,
|
|
86
|
-
onSuccess: factoryOnSuccess,
|
|
87
|
-
onCanisterError: factoryOnCanisterError,
|
|
88
|
-
onError: factoryOnError,
|
|
89
|
-
onMutate: factoryOnMutate,
|
|
90
|
-
onSettled: factoryOnSettled,
|
|
91
|
-
...factoryOptions
|
|
92
|
-
} = config
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Raw call without any invalidation logic.
|
|
96
|
-
* Used as mutationFn so that onSuccess handles all post-mutation work
|
|
97
|
-
* and there is no double-invalidation.
|
|
98
|
-
*/
|
|
99
|
-
const callFn = (
|
|
100
|
-
args: ReactorArgs<Service, Method, Transform>
|
|
101
|
-
): Promise<ReactorReturnOk<Service, Method, Transform>> =>
|
|
102
|
-
reactor.callMethod({ functionName, args, callConfig })
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The factory's own `onMutate` result for each call through the hook.
|
|
106
|
-
*
|
|
107
|
-
* TanStack Query stores one `onMutate` result per mutation and hands it to
|
|
108
|
-
* every callback. That slot holds the hook's result, which the mutation also
|
|
109
|
-
* exposes as `context`, so this map holds the factory's. Each key is the
|
|
110
|
-
* context object TanStack Query creates for a call and passes to every
|
|
111
|
-
* callback of that call.
|
|
112
|
-
*/
|
|
113
|
-
const factoryOnMutateResults = new WeakMap<
|
|
114
|
-
MutationFunctionContext,
|
|
115
|
-
TOnMutateResult | undefined
|
|
116
|
-
>()
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* The `onMutate` result a factory callback receives. A factory without its
|
|
120
|
-
* own `onMutate` gets the hook's, as it always has. TanStack Query before
|
|
121
|
-
* 5.89 passes no context, so there a factory callback gets the hook's result
|
|
122
|
-
* too.
|
|
123
|
-
*/
|
|
124
|
-
const factoryOnMutateResult = (
|
|
125
|
-
onMutateResult: unknown,
|
|
126
|
-
context: MutationFunctionContext | undefined
|
|
127
|
-
) =>
|
|
128
|
-
(factoryOnMutate && context
|
|
129
|
-
? factoryOnMutateResults.get(context)
|
|
130
|
-
: onMutateResult) as TOnMutateResult | undefined
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Imperative execution for non-React usage.
|
|
134
|
-
*
|
|
135
|
-
* Runs the same factory-level chain the hook does — invalidation, then
|
|
136
|
-
* `onSuccess`, or `onCanisterError`/`onError` on failure — so a mutation
|
|
137
|
-
* object behaves the same through both call paths. Only hook-level callbacks
|
|
138
|
-
* are absent, because there is no hook here to supply them.
|
|
139
|
-
*
|
|
140
|
-
* The callbacks get the same `{ client, meta, mutationKey }` context
|
|
141
|
-
* TanStack Query passes them on the hook path.
|
|
142
|
-
*
|
|
143
|
-
* The error is rethrown after the callbacks run, so `await execute(...)`
|
|
144
|
-
* still rejects for the caller.
|
|
145
|
-
*
|
|
146
|
-
* Use this in route loaders, scripts, or server-side code.
|
|
147
|
-
*/
|
|
148
|
-
const execute = async (
|
|
149
|
-
args: ReactorArgs<Service, Method, Transform>
|
|
150
|
-
): Promise<ReactorReturnOk<Service, Method, Transform>> => {
|
|
151
|
-
// TanStack Query builds this for the hook path, and the callback types
|
|
152
|
-
// declare it as always present. None of it is mutation state, so this path
|
|
153
|
-
// builds the same object. Passing `undefined` made a callback that reads
|
|
154
|
-
// `context.client` throw here and nowhere else.
|
|
155
|
-
//
|
|
156
|
-
// The hook path takes `meta` and `mutationKey` from the client's defaulted
|
|
157
|
-
// options, so this runs the same spread through `defaultMutationOptions`.
|
|
158
|
-
// Reading `factoryOptions` alone dropped mutation defaults registered on
|
|
159
|
-
// the QueryClient (`defaultOptions.mutations`, `setMutationDefaults`).
|
|
160
|
-
const effectiveOptions = reactor.queryClient.defaultMutationOptions({
|
|
161
|
-
mutationKey: reactor.getQueryOptions({ functionName }).queryKey,
|
|
162
|
-
...factoryOptions,
|
|
163
|
-
})
|
|
164
|
-
const context: MutationFunctionContext = {
|
|
165
|
-
client: reactor.queryClient,
|
|
166
|
-
meta: effectiveOptions.meta,
|
|
167
|
-
mutationKey: effectiveOptions.mutationKey,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let result: ReactorReturnOk<Service, Method, Transform>
|
|
171
|
-
try {
|
|
172
|
-
result = await callFn(args)
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (isCanisterError(error)) {
|
|
175
|
-
// `catch` drops the type. `callMethod` rejects with this method's
|
|
176
|
-
// CanisterError, the one the hook path narrows to.
|
|
177
|
-
factoryOnCanisterError?.(
|
|
178
|
-
error as Parameters<NonNullable<typeof factoryOnCanisterError>>[0],
|
|
179
|
-
args
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
// `onMutate` does not run on this path, so there is no result to pass.
|
|
183
|
-
// Awaited like `onSuccess` below and like the hook path, so an async
|
|
184
|
-
// rollback or report finishes before `execute()` rejects.
|
|
185
|
-
await factoryOnError?.(
|
|
186
|
-
error as Parameters<NonNullable<typeof factoryOnError>>[0],
|
|
187
|
-
args,
|
|
188
|
-
undefined,
|
|
189
|
-
context
|
|
190
|
-
)
|
|
191
|
-
throw error
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (factoryInvalidateQueries) {
|
|
195
|
-
await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
|
|
196
|
-
}
|
|
197
|
-
await factoryOnSuccess?.(result, args, undefined, context)
|
|
198
|
-
return result
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Hook implementation
|
|
202
|
-
const useMutationHook = <THookOnMutateResult = unknown>(
|
|
203
|
-
options?: MutationHookOptions<
|
|
204
|
-
Service,
|
|
205
|
-
Method,
|
|
206
|
-
Transform,
|
|
207
|
-
THookOnMutateResult
|
|
208
|
-
>
|
|
209
|
-
) => {
|
|
210
|
-
const baseOptions = reactor.getQueryOptions({ functionName })
|
|
211
|
-
const {
|
|
212
|
-
invalidateQueries: hookInvalidateQueries,
|
|
213
|
-
onCanisterError: hookOnCanisterError,
|
|
214
|
-
...restOptions
|
|
215
|
-
} = options ?? {}
|
|
216
|
-
|
|
217
|
-
return useMutation<
|
|
218
|
-
ReactorReturnOk<Service, Method, Transform>,
|
|
219
|
-
ReactorReturnErr<Service, Method, Transform>,
|
|
220
|
-
ReactorArgs<Service, Method, Transform>,
|
|
221
|
-
THookOnMutateResult
|
|
222
|
-
>(
|
|
223
|
-
{
|
|
224
|
-
mutationKey: baseOptions.queryKey,
|
|
225
|
-
...factoryOptions,
|
|
226
|
-
...restOptions,
|
|
227
|
-
// Use callFn (not execute) to avoid double-invalidation:
|
|
228
|
-
// factoryInvalidateQueries are handled in onSuccess below.
|
|
229
|
-
mutationFn: callFn,
|
|
230
|
-
onSuccess: async (data, variables, onMutateResult, context) => {
|
|
231
|
-
// 1. Factory-level invalidation
|
|
232
|
-
if (factoryInvalidateQueries) {
|
|
233
|
-
await invalidateAll(reactor.queryClient, factoryInvalidateQueries)
|
|
234
|
-
}
|
|
235
|
-
// 2. Hook-level invalidation
|
|
236
|
-
if (hookInvalidateQueries) {
|
|
237
|
-
await invalidateAll(reactor.queryClient, hookInvalidateQueries)
|
|
238
|
-
}
|
|
239
|
-
// 3. Factory onSuccess
|
|
240
|
-
await factoryOnSuccess?.(
|
|
241
|
-
data,
|
|
242
|
-
variables,
|
|
243
|
-
factoryOnMutateResult(onMutateResult, context),
|
|
244
|
-
context
|
|
245
|
-
)
|
|
246
|
-
// 4. Hook onSuccess
|
|
247
|
-
await restOptions.onSuccess?.(
|
|
248
|
-
data,
|
|
249
|
-
variables,
|
|
250
|
-
onMutateResult,
|
|
251
|
-
context
|
|
252
|
-
)
|
|
253
|
-
},
|
|
254
|
-
onError: async (error, variables, onMutateResult, context) => {
|
|
255
|
-
if (isCanisterError(error)) {
|
|
256
|
-
factoryOnCanisterError?.(error, variables)
|
|
257
|
-
hookOnCanisterError?.(error, variables)
|
|
258
|
-
}
|
|
259
|
-
// Awaited in order, like `onSuccess`. TanStack Query holds
|
|
260
|
-
// `onSettled` and the settled state until this promise resolves, so
|
|
261
|
-
// an async `onError` must be part of it.
|
|
262
|
-
await factoryOnError?.(
|
|
263
|
-
error,
|
|
264
|
-
variables,
|
|
265
|
-
factoryOnMutateResult(onMutateResult, context),
|
|
266
|
-
context
|
|
267
|
-
)
|
|
268
|
-
await restOptions.onError?.(error, variables, onMutateResult, context)
|
|
269
|
-
},
|
|
270
|
-
// `onMutate` and `onSettled` are composed like `onSuccess`/`onError`
|
|
271
|
-
// above. They used to arrive through the `...restOptions` spread, so a
|
|
272
|
-
// hook-level one silently replaced the factory's — factory teardown,
|
|
273
|
-
// telemetry or logging simply vanished the moment any call site passed
|
|
274
|
-
// its own, with no warning and no type error. Chaining is what a
|
|
275
|
-
// reader who has seen `onSuccess` chain already expects.
|
|
276
|
-
onMutate: async (variables, context) => {
|
|
277
|
-
if (factoryOnMutate) {
|
|
278
|
-
const result = await factoryOnMutate(variables, context)
|
|
279
|
-
if (context) factoryOnMutateResults.set(context, result)
|
|
280
|
-
}
|
|
281
|
-
// Without a hook-level `onMutate` this is `undefined`, and
|
|
282
|
-
// THookOnMutateResult is then `unknown`.
|
|
283
|
-
return (await restOptions.onMutate?.(
|
|
284
|
-
variables,
|
|
285
|
-
context
|
|
286
|
-
)) as THookOnMutateResult
|
|
287
|
-
},
|
|
288
|
-
onSettled: async (data, error, variables, onMutateResult, context) => {
|
|
289
|
-
await factoryOnSettled?.(
|
|
290
|
-
data,
|
|
291
|
-
error,
|
|
292
|
-
variables,
|
|
293
|
-
factoryOnMutateResult(onMutateResult, context),
|
|
294
|
-
context
|
|
295
|
-
)
|
|
296
|
-
await restOptions.onSettled?.(
|
|
297
|
-
data,
|
|
298
|
-
error,
|
|
299
|
-
variables,
|
|
300
|
-
onMutateResult,
|
|
301
|
-
context
|
|
302
|
-
)
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
reactor.queryClient
|
|
306
|
-
)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return { useMutation: useMutationHook, execute }
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ============================================================================
|
|
313
|
-
// Public Factory Function
|
|
314
|
-
// ============================================================================
|
|
315
|
-
|
|
316
|
-
export function createMutation<
|
|
317
|
-
Service,
|
|
318
|
-
Transform extends TransformKey,
|
|
319
|
-
Method extends FunctionName<Service> = FunctionName<Service>,
|
|
320
|
-
TOnMutateResult = unknown,
|
|
321
|
-
>(
|
|
322
|
-
reactor: Reactor<Service, Transform>,
|
|
323
|
-
// `execute()` runs the factory's `onSuccess` and `onError` without running
|
|
324
|
-
// its `onMutate`, so their `onMutate` result can also be `undefined`.
|
|
325
|
-
config: MutationConfig<
|
|
326
|
-
NoInfer<Service>,
|
|
327
|
-
Method,
|
|
328
|
-
Transform,
|
|
329
|
-
TOnMutateResult | undefined
|
|
330
|
-
>
|
|
331
|
-
): MutationResult<Service, Method, Transform> {
|
|
332
|
-
return createMutationImpl(
|
|
333
|
-
reactor,
|
|
334
|
-
config as MutationConfig<
|
|
335
|
-
Service,
|
|
336
|
-
Method,
|
|
337
|
-
Transform,
|
|
338
|
-
TOnMutateResult | undefined
|
|
339
|
-
>
|
|
340
|
-
)
|
|
341
|
-
}
|
package/src/createQuery.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query Factory - Generic wrapper for React Query-based canister data
|
|
3
|
-
*
|
|
4
|
-
* Creates unified fetch/hook/invalidate functions for any canister method.
|
|
5
|
-
* Works with any Reactor instance.
|
|
6
|
-
* Use this when the same read operation must work both inside React components
|
|
7
|
-
* and outside React (loaders, actions, services, tests).
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* const userQuery = createQuery(todoManager, {
|
|
11
|
-
* functionName: "get_user",
|
|
12
|
-
* select: (result) => result.user,
|
|
13
|
-
* })
|
|
14
|
-
*
|
|
15
|
-
* // In component
|
|
16
|
-
* const { data: user } = userQuery.useQuery()
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* const userQuery = createQuery(todoManager, { functionName: "get_user", args: ["alice"] })
|
|
20
|
-
*
|
|
21
|
-
* // Outside React (loader/service/script)
|
|
22
|
-
* await userQuery.fetch()
|
|
23
|
-
* const cached = userQuery.getCacheData()
|
|
24
|
-
* await userQuery.invalidate()
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import type {
|
|
28
|
-
Reactor,
|
|
29
|
-
FunctionName,
|
|
30
|
-
ReactorArgs,
|
|
31
|
-
TransformKey,
|
|
32
|
-
} from "@ic-reactor/core"
|
|
33
|
-
import { useMemo } from "react"
|
|
34
|
-
import {
|
|
35
|
-
QueryKey,
|
|
36
|
-
useQuery,
|
|
37
|
-
type UseQueryOptions,
|
|
38
|
-
type Updater,
|
|
39
|
-
} from "@tanstack/react-query"
|
|
40
|
-
import type {
|
|
41
|
-
QueryFnData,
|
|
42
|
-
QueryError,
|
|
43
|
-
QueryConfig,
|
|
44
|
-
UseQueryWithSelect,
|
|
45
|
-
QueryResult,
|
|
46
|
-
QueryFactoryConfig,
|
|
47
|
-
NoInfer,
|
|
48
|
-
} from "./types.js"
|
|
49
|
-
import { buildChainedSelect, createBoundedCache } from "./utils.js"
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// Internal Implementation
|
|
53
|
-
// ============================================================================
|
|
54
|
-
|
|
55
|
-
const createQueryImpl = <
|
|
56
|
-
Service,
|
|
57
|
-
Method extends FunctionName<Service> = FunctionName<Service>,
|
|
58
|
-
Transform extends TransformKey = "candid",
|
|
59
|
-
Selected = QueryFnData<Service, Method, Transform>,
|
|
60
|
-
>(
|
|
61
|
-
reactor: Reactor<Service, Transform>,
|
|
62
|
-
config: QueryConfig<Service, Method, Transform, Selected>
|
|
63
|
-
): QueryResult<
|
|
64
|
-
QueryFnData<Service, Method, Transform>,
|
|
65
|
-
Selected,
|
|
66
|
-
QueryError<Service, Method, Transform>
|
|
67
|
-
> => {
|
|
68
|
-
type TData = QueryFnData<Service, Method, Transform>
|
|
69
|
-
type TError = QueryError<Service, Method, Transform>
|
|
70
|
-
|
|
71
|
-
const {
|
|
72
|
-
functionName,
|
|
73
|
-
args,
|
|
74
|
-
staleTime = 5 * 60 * 1000,
|
|
75
|
-
select,
|
|
76
|
-
queryKey: customQueryKey,
|
|
77
|
-
...rest
|
|
78
|
-
} = config
|
|
79
|
-
|
|
80
|
-
const params = { functionName, args, queryKey: customQueryKey }
|
|
81
|
-
|
|
82
|
-
const getQueryKey = (): QueryKey => reactor.generateQueryKey(params)
|
|
83
|
-
|
|
84
|
-
// Apply config.select to raw data (shared by fetch, getCacheData, and the hook)
|
|
85
|
-
const applySelect = (raw: TData): Selected =>
|
|
86
|
-
select ? select(raw) : (raw as unknown as Selected)
|
|
87
|
-
|
|
88
|
-
/** Cache-first fetch for use in loaders / route preloading. */
|
|
89
|
-
const fetch = async (): Promise<Selected> => {
|
|
90
|
-
const result = await reactor.fetchQuery(params)
|
|
91
|
-
return applySelect(result)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Fire-and-forget prefetch — warms the cache without blocking. */
|
|
95
|
-
const prefetch = (): Promise<void> => {
|
|
96
|
-
const baseOptions = reactor.getQueryOptions(params)
|
|
97
|
-
return reactor.queryClient.prefetchQuery({
|
|
98
|
-
queryKey: baseOptions.queryKey,
|
|
99
|
-
queryFn: baseOptions.queryFn,
|
|
100
|
-
staleTime,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// The hook publicly exposes the overloaded UseQueryWithSelect signature.
|
|
105
|
-
// Internally it takes a single broad options object, so we type the
|
|
106
|
-
// implementation explicitly and cast once to the public overloaded type.
|
|
107
|
-
type UseQueryHookOptions = Omit<
|
|
108
|
-
UseQueryOptions<TData, TError, unknown>,
|
|
109
|
-
"queryKey" | "queryFn"
|
|
110
|
-
> & { select?: (data: Selected) => unknown }
|
|
111
|
-
|
|
112
|
-
const useQueryHook = ((options?: UseQueryHookOptions) => {
|
|
113
|
-
const baseOptions = reactor.getQueryOptions(params)
|
|
114
|
-
// Memoized so the observer's select-result cache can hit; see
|
|
115
|
-
// buildChainedSelect. `select` comes from the factory config and is stable.
|
|
116
|
-
const chainedSelect = useMemo(
|
|
117
|
-
() => buildChainedSelect(select, options?.select),
|
|
118
|
-
[options?.select]
|
|
119
|
-
)
|
|
120
|
-
return useQuery(
|
|
121
|
-
{
|
|
122
|
-
queryKey: baseOptions.queryKey,
|
|
123
|
-
staleTime,
|
|
124
|
-
...rest,
|
|
125
|
-
...options,
|
|
126
|
-
queryFn: baseOptions.queryFn,
|
|
127
|
-
select: chainedSelect,
|
|
128
|
-
},
|
|
129
|
-
reactor.queryClient
|
|
130
|
-
)
|
|
131
|
-
}) as UseQueryWithSelect<TData, Selected, TError>
|
|
132
|
-
|
|
133
|
-
const invalidate = async (): Promise<void> => {
|
|
134
|
-
await reactor.queryClient.invalidateQueries({ queryKey: getQueryKey() })
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const getCacheData = ((
|
|
138
|
-
selectFn?: (data: Selected) => unknown
|
|
139
|
-
): Selected | unknown => {
|
|
140
|
-
const raw = reactor.getQueryData(params)
|
|
141
|
-
if (raw === undefined) return undefined
|
|
142
|
-
const selected = applySelect(raw)
|
|
143
|
-
return selectFn ? selectFn(selected) : selected
|
|
144
|
-
}) as QueryResult<TData, Selected, TError>["getCacheData"]
|
|
145
|
-
|
|
146
|
-
const setData: QueryResult<TData, Selected, TError>["setData"] = (
|
|
147
|
-
updater
|
|
148
|
-
) => {
|
|
149
|
-
return reactor.queryClient.setQueryData(
|
|
150
|
-
getQueryKey(),
|
|
151
|
-
updater as Updater<TData | undefined, TData | undefined>
|
|
152
|
-
) as TData | undefined
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
fetch,
|
|
157
|
-
prefetch,
|
|
158
|
-
useQuery: useQueryHook,
|
|
159
|
-
invalidate,
|
|
160
|
-
getQueryKey,
|
|
161
|
-
getCacheData,
|
|
162
|
-
setData,
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// Public Factory Function
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
|
-
export function createQuery<
|
|
171
|
-
Service,
|
|
172
|
-
Transform extends TransformKey,
|
|
173
|
-
Method extends FunctionName<Service> = FunctionName<Service>,
|
|
174
|
-
Selected = QueryFnData<Service, Method, Transform>,
|
|
175
|
-
>(
|
|
176
|
-
reactor: Reactor<Service, Transform>,
|
|
177
|
-
config: QueryConfig<NoInfer<Service>, Method, Transform, Selected>
|
|
178
|
-
): QueryResult<
|
|
179
|
-
QueryFnData<Service, Method, Transform>,
|
|
180
|
-
Selected,
|
|
181
|
-
QueryError<Service, Method, Transform>
|
|
182
|
-
> {
|
|
183
|
-
return createQueryImpl(
|
|
184
|
-
reactor,
|
|
185
|
-
config as QueryConfig<Service, Method, Transform, Selected>
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ============================================================================
|
|
190
|
-
// Convenience: Create query with dynamic args
|
|
191
|
-
// ============================================================================
|
|
192
|
-
|
|
193
|
-
export function createQueryFactory<
|
|
194
|
-
Service,
|
|
195
|
-
Transform extends TransformKey,
|
|
196
|
-
Method extends FunctionName<Service> = FunctionName<Service>,
|
|
197
|
-
Selected = QueryFnData<Service, Method, Transform>,
|
|
198
|
-
>(
|
|
199
|
-
reactor: Reactor<Service, Transform>,
|
|
200
|
-
config: QueryFactoryConfig<NoInfer<Service>, Method, Transform, Selected>
|
|
201
|
-
): (
|
|
202
|
-
args: ReactorArgs<Service, Method, Transform>
|
|
203
|
-
) => QueryResult<
|
|
204
|
-
QueryFnData<Service, Method, Transform>,
|
|
205
|
-
Selected,
|
|
206
|
-
QueryError<Service, Method, Transform>
|
|
207
|
-
> {
|
|
208
|
-
const cache =
|
|
209
|
-
createBoundedCache<
|
|
210
|
-
QueryResult<
|
|
211
|
-
QueryFnData<Service, Method, Transform>,
|
|
212
|
-
Selected,
|
|
213
|
-
QueryError<Service, Method, Transform>
|
|
214
|
-
>
|
|
215
|
-
>()
|
|
216
|
-
|
|
217
|
-
return (args: ReactorArgs<Service, Method, Transform>) => {
|
|
218
|
-
const key = reactor.generateQueryKey({
|
|
219
|
-
functionName: config.functionName as Method,
|
|
220
|
-
args,
|
|
221
|
-
})
|
|
222
|
-
const cacheKey = JSON.stringify(key)
|
|
223
|
-
|
|
224
|
-
const existing = cache.get(cacheKey)
|
|
225
|
-
if (existing) return existing
|
|
226
|
-
|
|
227
|
-
const result = createQueryImpl<Service, Method, Transform, Selected>(
|
|
228
|
-
reactor,
|
|
229
|
-
{
|
|
230
|
-
...config,
|
|
231
|
-
args,
|
|
232
|
-
}
|
|
233
|
-
)
|
|
234
|
-
cache.set(cacheKey, result)
|
|
235
|
-
return result
|
|
236
|
-
}
|
|
237
|
-
}
|