@ic-reactor/react 3.0.3-beta.4 → 3.0.3

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/README.md +83 -14
  2. package/dist/createInfiniteQuery.d.ts +23 -10
  3. package/dist/createInfiniteQuery.d.ts.map +1 -1
  4. package/dist/createInfiniteQuery.js +24 -3
  5. package/dist/createInfiniteQuery.js.map +1 -1
  6. package/dist/createMutation.d.ts.map +1 -1
  7. package/dist/createMutation.js +11 -1
  8. package/dist/createMutation.js.map +1 -1
  9. package/dist/createSuspenseInfiniteQuery.d.ts +29 -5
  10. package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
  11. package/dist/createSuspenseInfiniteQuery.js +31 -3
  12. package/dist/createSuspenseInfiniteQuery.js.map +1 -1
  13. package/dist/hooks/index.d.ts +18 -6
  14. package/dist/hooks/index.d.ts.map +1 -1
  15. package/dist/hooks/index.js +15 -6
  16. package/dist/hooks/index.js.map +1 -1
  17. package/dist/hooks/useActorInfiniteQuery.d.ts +13 -11
  18. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  19. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  20. package/dist/hooks/useActorMethod.d.ts +20 -24
  21. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  22. package/dist/hooks/useActorMethod.js +29 -18
  23. package/dist/hooks/useActorMethod.js.map +1 -1
  24. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +13 -10
  25. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  26. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  27. package/package.json +8 -7
  28. package/src/createActorHooks.ts +146 -0
  29. package/src/createAuthHooks.ts +137 -0
  30. package/src/createInfiniteQuery.ts +522 -0
  31. package/src/createMutation.ts +173 -0
  32. package/src/createQuery.ts +197 -0
  33. package/src/createSuspenseInfiniteQuery.ts +556 -0
  34. package/src/createSuspenseQuery.ts +215 -0
  35. package/src/hooks/index.ts +93 -0
  36. package/src/hooks/useActorInfiniteQuery.test.tsx +457 -0
  37. package/src/hooks/useActorInfiniteQuery.ts +134 -0
  38. package/src/hooks/useActorMethod.test.tsx +798 -0
  39. package/src/hooks/useActorMethod.ts +397 -0
  40. package/src/hooks/useActorMutation.test.tsx +220 -0
  41. package/src/hooks/useActorMutation.ts +124 -0
  42. package/src/hooks/useActorQuery.test.tsx +287 -0
  43. package/src/hooks/useActorQuery.ts +110 -0
  44. package/src/hooks/useActorSuspenseInfiniteQuery.test.tsx +472 -0
  45. package/src/hooks/useActorSuspenseInfiniteQuery.ts +137 -0
  46. package/src/hooks/useActorSuspenseQuery.test.tsx +254 -0
  47. package/src/hooks/useActorSuspenseQuery.ts +112 -0
  48. package/src/index.ts +21 -0
  49. package/src/types.ts +435 -0
  50. package/src/validation.ts +202 -0
@@ -0,0 +1,173 @@
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
+ *
7
+ * @example
8
+ * const transferMutation = createMutation(reactor, {
9
+ * functionName: "transfer",
10
+ * onSuccess: () => console.log("Success!"),
11
+ * })
12
+ *
13
+ * // In component
14
+ * const { mutate, isPending } = transferMutation.useMutation()
15
+ */
16
+
17
+ import { useMutation } from "@tanstack/react-query"
18
+ import type {
19
+ Reactor,
20
+ FunctionName,
21
+ ReactorArgs,
22
+ TransformKey,
23
+ ReactorReturnOk,
24
+ } from "@ic-reactor/core"
25
+ import { isCanisterError } from "@ic-reactor/core"
26
+ import type {
27
+ MutationConfig,
28
+ MutationResult,
29
+ MutationHookOptions,
30
+ NoInfer,
31
+ } from "./types"
32
+
33
+ // ============================================================================
34
+ // Internal Implementation
35
+ // ============================================================================
36
+
37
+ const createMutationImpl = <
38
+ A,
39
+ M extends FunctionName<A> = FunctionName<A>,
40
+ T extends TransformKey = "candid",
41
+ >(
42
+ reactor: Reactor<A, T>,
43
+ config: MutationConfig<A, M, T>
44
+ ): MutationResult<A, M, T> => {
45
+ const {
46
+ functionName,
47
+ callConfig,
48
+ invalidateQueries: factoryInvalidateQueries,
49
+ onSuccess: factoryOnSuccess,
50
+ onCanisterError: factoryOnCanisterError,
51
+ onError: factoryOnError,
52
+ ...factoryOptions
53
+ } = config
54
+
55
+ // Direct execution function
56
+ const execute = async (
57
+ args: ReactorArgs<A, M, T>
58
+ ): Promise<ReactorReturnOk<A, M, T>> => {
59
+ const result = await reactor.callMethod({
60
+ functionName,
61
+ args,
62
+ callConfig,
63
+ })
64
+
65
+ if (factoryInvalidateQueries) {
66
+ await Promise.all(
67
+ factoryInvalidateQueries.map((queryKey) => {
68
+ return reactor.queryClient.invalidateQueries({ queryKey })
69
+ })
70
+ )
71
+ }
72
+
73
+ return result
74
+ }
75
+
76
+ // Hook implementation
77
+ const useMutationHook = (options?: MutationHookOptions<A, M, T>) => {
78
+ const baseOptions = reactor.getQueryOptions({
79
+ functionName,
80
+ })
81
+ // Extract our custom options
82
+ const {
83
+ invalidateQueries: hookInvalidateQueries,
84
+ onCanisterError: hookOnCanisterError,
85
+ ...restOptions
86
+ } = options || {}
87
+
88
+ return useMutation(
89
+ {
90
+ mutationKey: baseOptions.queryKey,
91
+ ...factoryOptions,
92
+ ...restOptions,
93
+ mutationFn: execute,
94
+ onSuccess: async (...args) => {
95
+ // 1. Handle factory-level invalidateQueries
96
+ if (factoryInvalidateQueries) {
97
+ await Promise.all(
98
+ factoryInvalidateQueries.map((queryKey) => {
99
+ return reactor.queryClient.invalidateQueries({ queryKey })
100
+ })
101
+ )
102
+ }
103
+
104
+ // 2. Handle hook-level invalidateQueries
105
+ 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)
124
+ }
125
+ },
126
+ onError: (error, variables, context, mutation) => {
127
+ // Check if this is a CanisterError (from Result { Err: E })
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)
147
+ }
148
+ },
149
+ },
150
+ reactor.queryClient
151
+ )
152
+ }
153
+
154
+ return {
155
+ useMutation: useMutationHook,
156
+ execute,
157
+ }
158
+ }
159
+
160
+ // ============================================================================
161
+ // Factory Function
162
+ // ============================================================================
163
+
164
+ export function createMutation<
165
+ A,
166
+ T extends TransformKey,
167
+ M extends FunctionName<A> = FunctionName<A>,
168
+ >(
169
+ reactor: Reactor<A, T>,
170
+ config: MutationConfig<NoInfer<A>, M, T>
171
+ ): MutationResult<A, M, T> {
172
+ return createMutationImpl(reactor, config as MutationConfig<A, M, T>)
173
+ }
@@ -0,0 +1,197 @@
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
+ *
7
+ * @example
8
+ * const userQuery = createQuery(todoManager, {
9
+ * functionName: "get_user",
10
+ * select: (result) => result.user,
11
+ * })
12
+ *
13
+ * // In component
14
+ * const { data: user } = userQuery.useQuery()
15
+ */
16
+
17
+ import type {
18
+ Reactor,
19
+ FunctionName,
20
+ ReactorArgs,
21
+ TransformKey,
22
+ } from "@ic-reactor/core"
23
+ import { QueryKey, useQuery } from "@tanstack/react-query"
24
+ import type {
25
+ QueryFnData,
26
+ QueryError,
27
+ QueryConfig,
28
+ UseQueryWithSelect,
29
+ QueryResult,
30
+ QueryFactoryConfig,
31
+ NoInfer,
32
+ } from "./types"
33
+
34
+ // ============================================================================
35
+ // Internal Implementation
36
+ // ============================================================================
37
+
38
+ const createQueryImpl = <
39
+ A,
40
+ M extends FunctionName<A> = FunctionName<A>,
41
+ T extends TransformKey = "candid",
42
+ TSelected = QueryFnData<A, M, T>,
43
+ >(
44
+ reactor: Reactor<A, T>,
45
+ config: QueryConfig<A, M, T, TSelected>
46
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> => {
47
+ type TData = QueryFnData<A, M, T>
48
+ type TError = QueryError<A, M, T>
49
+
50
+ const {
51
+ functionName,
52
+ args,
53
+ staleTime = 5 * 60 * 1000,
54
+ select,
55
+ queryKey: customQueryKey,
56
+ ...rest
57
+ } = config
58
+
59
+ const params = {
60
+ functionName,
61
+ args,
62
+ queryKey: customQueryKey,
63
+ }
64
+
65
+ // Get query key from actor manager
66
+ const getQueryKey = (): QueryKey => {
67
+ return reactor.generateQueryKey(params)
68
+ }
69
+
70
+ // Fetch function for loaders (cache-first)
71
+ const fetch = async (): Promise<TSelected> => {
72
+ const result = await reactor.fetchQuery(params)
73
+ return select ? select(result) : (result as TSelected)
74
+ }
75
+
76
+ // Implementation
77
+ const useQueryHook: UseQueryWithSelect<TData, TSelected, TError> = (
78
+ options: any
79
+ ): any => {
80
+ 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
+ return useQuery(
91
+ {
92
+ queryKey: baseOptions.queryKey,
93
+ staleTime,
94
+ ...rest,
95
+ ...options,
96
+ queryFn: baseOptions.queryFn,
97
+ select: chainedSelect,
98
+ },
99
+ reactor.queryClient
100
+ )
101
+ }
102
+
103
+ // Invalidate function
104
+ const invalidate = async (): Promise<void> => {
105
+ const queryKey = getQueryKey()
106
+ await reactor.queryClient.invalidateQueries({ queryKey })
107
+ }
108
+
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
+ }
126
+
127
+ return selectedData
128
+ }
129
+
130
+ return {
131
+ fetch,
132
+ useQuery: useQueryHook,
133
+ invalidate,
134
+ getQueryKey,
135
+ getCacheData,
136
+ }
137
+ }
138
+
139
+ // ============================================================================
140
+ // Factory Function
141
+ // ============================================================================
142
+
143
+ export function createQuery<
144
+ A,
145
+ T extends TransformKey,
146
+ M extends FunctionName<A> = FunctionName<A>,
147
+ TSelected = QueryFnData<A, M, T>,
148
+ >(
149
+ reactor: Reactor<A, T>,
150
+ config: QueryConfig<NoInfer<A>, M, T, TSelected>
151
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
152
+ return createQueryImpl(reactor, config as QueryConfig<A, M, T, TSelected>)
153
+ }
154
+
155
+ // ============================================================================
156
+ // Convenience: Create query with dynamic args
157
+ // ============================================================================
158
+
159
+ export function createQueryFactory<
160
+ A,
161
+ T extends TransformKey,
162
+ M extends FunctionName<A> = FunctionName<A>,
163
+ TSelected = QueryFnData<A, M, T>,
164
+ >(
165
+ reactor: Reactor<A, T>,
166
+ config: QueryFactoryConfig<NoInfer<A>, M, T, TSelected>
167
+ ): (
168
+ args: ReactorArgs<A, M, T>
169
+ ) => QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> {
170
+ const cache = new Map<
171
+ string,
172
+ QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>>
173
+ >()
174
+
175
+ return (
176
+ args: ReactorArgs<A, M, T>
177
+ ): QueryResult<QueryFnData<A, M, T>, TSelected, QueryError<A, M, T>> => {
178
+ const key = reactor.generateQueryKey({
179
+ functionName: config.functionName as M,
180
+ args,
181
+ })
182
+ const cacheKey = JSON.stringify(key)
183
+
184
+ if (cache.has(cacheKey)) {
185
+ return cache.get(cacheKey)!
186
+ }
187
+
188
+ const result = createQueryImpl<A, M, T, TSelected>(reactor, {
189
+ ...config,
190
+ args,
191
+ })
192
+
193
+ cache.set(cacheKey, result)
194
+
195
+ return result
196
+ }
197
+ }