@ic-reactor/codegen 0.1.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.
- package/dist/index.cjs +543 -0
- package/dist/index.d.cts +237 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.js +489 -0
- package/package.json +60 -0
- package/src/bindgen.ts +113 -0
- package/src/did.test.ts +102 -0
- package/src/did.ts +85 -0
- package/src/index.ts +51 -0
- package/src/naming.test.ts +59 -0
- package/src/naming.ts +83 -0
- package/src/templates/__snapshots__/infiniteQuery.test.ts.snap +44 -0
- package/src/templates/__snapshots__/mutation.test.ts.snap +59 -0
- package/src/templates/__snapshots__/query.test.ts.snap +64 -0
- package/src/templates/__snapshots__/reactor.test.ts.snap +163 -0
- package/src/templates/infiniteQuery.test.ts +42 -0
- package/src/templates/infiniteQuery.ts +74 -0
- package/src/templates/mutation.test.ts +43 -0
- package/src/templates/mutation.ts +51 -0
- package/src/templates/query.test.ts +44 -0
- package/src/templates/query.ts +71 -0
- package/src/templates/reactor.test.ts +78 -0
- package/src/templates/reactor.ts +290 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Reactor File Generation > Advanced Mode > generates per-method hooks correctly 1`] = `
|
|
4
|
+
"/**
|
|
5
|
+
* MyCanister Reactor (Advanced)
|
|
6
|
+
*
|
|
7
|
+
* Auto-generated by @ic-reactor/codegen
|
|
8
|
+
* Includes reactor instance, actor hooks, and per-method static hooks.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
DisplayReactor,
|
|
13
|
+
createActorHooks,
|
|
14
|
+
createQuery,
|
|
15
|
+
createMutation,
|
|
16
|
+
} from "@ic-reactor/react"
|
|
17
|
+
import { clientManager } from "../../client"
|
|
18
|
+
import { idlFactory, type _SERVICE } from "./declarations/my_canister.did"
|
|
19
|
+
|
|
20
|
+
type MyCanisterService = _SERVICE
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* MyCanister Reactor — Display mode.
|
|
24
|
+
* Automatically converts bigint → string, Principal → string, etc.
|
|
25
|
+
*/
|
|
26
|
+
export const myCanisterReactor = new DisplayReactor<MyCanisterService>({
|
|
27
|
+
clientManager,
|
|
28
|
+
idlFactory,
|
|
29
|
+
name: "my_canister",
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
useActorQuery: useMyCanisterQuery,
|
|
34
|
+
useActorSuspenseQuery: useMyCanisterSuspenseQuery,
|
|
35
|
+
useActorInfiniteQuery: useMyCanisterInfiniteQuery,
|
|
36
|
+
useActorSuspenseInfiniteQuery: useMyCanisterSuspenseInfiniteQuery,
|
|
37
|
+
useActorMutation: useMyCanisterMutation,
|
|
38
|
+
useActorMethod: useMyCanisterMethod,
|
|
39
|
+
} = createActorHooks(myCanisterReactor)
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
useMyCanisterQuery,
|
|
43
|
+
useMyCanisterSuspenseQuery,
|
|
44
|
+
useMyCanisterInfiniteQuery,
|
|
45
|
+
useMyCanisterSuspenseInfiniteQuery,
|
|
46
|
+
useMyCanisterMutation,
|
|
47
|
+
useMyCanisterMethod,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Per-method static hooks (no-args methods only)
|
|
51
|
+
|
|
52
|
+
export const listItemsQuery = createQuery(myCanisterReactor, {
|
|
53
|
+
functionName: "list_items",
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export const updateStatusMutation = createMutation(myCanisterReactor, {
|
|
57
|
+
functionName: "update_status",
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export { idlFactory }
|
|
61
|
+
export type { MyCanisterService }
|
|
62
|
+
"
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
exports[`Reactor File Generation > Fallback Mode > generates correctly without declarations 1`] = `
|
|
66
|
+
"/**
|
|
67
|
+
* MyCanister Reactor
|
|
68
|
+
*
|
|
69
|
+
* Auto-generated by @ic-reactor/codegen
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ Declarations were not generated. Run:
|
|
72
|
+
* npx @icp-sdk/bindgen --input <path-to-did> --output ./my_canister/declarations
|
|
73
|
+
* Then uncomment the import below and remove the fallback type.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
|
|
77
|
+
import { clientManager } from "../../client"
|
|
78
|
+
|
|
79
|
+
// TODO: Uncomment after generating declarations:
|
|
80
|
+
// import { idlFactory, type _SERVICE as MyCanisterService } from "./declarations/my_canister.did"
|
|
81
|
+
|
|
82
|
+
// Fallback — replace with generated types
|
|
83
|
+
type MyCanisterService = Record<string, (...args: unknown[]) => Promise<unknown>>
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const idlFactory = ({ IDL }: { IDL: any }) => IDL.Service({})
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* MyCanister Reactor — Display mode.
|
|
89
|
+
* Automatically converts bigint → string, Principal → string, etc.
|
|
90
|
+
*/
|
|
91
|
+
export const myCanisterReactor = new DisplayReactor<MyCanisterService>({
|
|
92
|
+
clientManager,
|
|
93
|
+
idlFactory,
|
|
94
|
+
name: "my_canister",
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const {
|
|
98
|
+
useActorQuery: useMyCanisterQuery,
|
|
99
|
+
useActorSuspenseQuery: useMyCanisterSuspenseQuery,
|
|
100
|
+
useActorInfiniteQuery: useMyCanisterInfiniteQuery,
|
|
101
|
+
useActorSuspenseInfiniteQuery: useMyCanisterSuspenseInfiniteQuery,
|
|
102
|
+
useActorMutation: useMyCanisterMutation,
|
|
103
|
+
useActorMethod: useMyCanisterMethod,
|
|
104
|
+
} = createActorHooks(myCanisterReactor)
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
useMyCanisterQuery,
|
|
108
|
+
useMyCanisterSuspenseQuery,
|
|
109
|
+
useMyCanisterInfiniteQuery,
|
|
110
|
+
useMyCanisterSuspenseInfiniteQuery,
|
|
111
|
+
useMyCanisterMutation,
|
|
112
|
+
useMyCanisterMethod,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { idlFactory }
|
|
116
|
+
export type { MyCanisterService }
|
|
117
|
+
"
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
exports[`Reactor File Generation > Simple Mode > generates correctly 1`] = `
|
|
121
|
+
"/**
|
|
122
|
+
* MyCanister Reactor
|
|
123
|
+
*
|
|
124
|
+
* Auto-generated by @ic-reactor/codegen
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
|
|
128
|
+
import { clientManager } from "../../client"
|
|
129
|
+
import { idlFactory, type _SERVICE } from "./declarations/my_canister.did"
|
|
130
|
+
|
|
131
|
+
export type MyCanisterService = _SERVICE
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* MyCanister Reactor — Display mode.
|
|
135
|
+
* Automatically converts bigint → string, Principal → string, etc.
|
|
136
|
+
*/
|
|
137
|
+
export const myCanisterReactor = new DisplayReactor<MyCanisterService>({
|
|
138
|
+
clientManager,
|
|
139
|
+
idlFactory,
|
|
140
|
+
name: "my_canister",
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const {
|
|
144
|
+
useActorQuery: useMyCanisterQuery,
|
|
145
|
+
useActorSuspenseQuery: useMyCanisterSuspenseQuery,
|
|
146
|
+
useActorInfiniteQuery: useMyCanisterInfiniteQuery,
|
|
147
|
+
useActorSuspenseInfiniteQuery: useMyCanisterSuspenseInfiniteQuery,
|
|
148
|
+
useActorMutation: useMyCanisterMutation,
|
|
149
|
+
useActorMethod: useMyCanisterMethod,
|
|
150
|
+
} = createActorHooks(myCanisterReactor)
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
useMyCanisterQuery,
|
|
154
|
+
useMyCanisterSuspenseQuery,
|
|
155
|
+
useMyCanisterInfiniteQuery,
|
|
156
|
+
useMyCanisterSuspenseInfiniteQuery,
|
|
157
|
+
useMyCanisterMutation,
|
|
158
|
+
useMyCanisterMethod,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { idlFactory }
|
|
162
|
+
"
|
|
163
|
+
`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
generateInfiniteQueryHook,
|
|
4
|
+
InfiniteQueryHookOptions,
|
|
5
|
+
} from "./infiniteQuery"
|
|
6
|
+
import type { MethodInfo } from "../types"
|
|
7
|
+
|
|
8
|
+
describe("Infinite Query Hook Generation", () => {
|
|
9
|
+
const listItems: MethodInfo = {
|
|
10
|
+
name: "list_items",
|
|
11
|
+
type: "query",
|
|
12
|
+
hasArgs: true,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
it("generates infinite query hook correctly", () => {
|
|
16
|
+
const options: InfiniteQueryHookOptions = {
|
|
17
|
+
canisterName: "my_canister",
|
|
18
|
+
method: listItems,
|
|
19
|
+
type: "infiniteQuery",
|
|
20
|
+
}
|
|
21
|
+
const result = generateInfiniteQueryHook(options)
|
|
22
|
+
expect(result).toMatchSnapshot()
|
|
23
|
+
expect(result).toContain("createInfiniteQuery(myCanisterReactor, {")
|
|
24
|
+
expect(result).toContain(
|
|
25
|
+
"export const useListItemsInfiniteQuery = listItemsInfiniteQuery.useInfiniteQuery"
|
|
26
|
+
)
|
|
27
|
+
// Should contain default pagination logic placeholders
|
|
28
|
+
expect(result).toContain("initialPageParam: 0 as PageCursor")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("handles suspense infinite query type", () => {
|
|
32
|
+
const options: InfiniteQueryHookOptions = {
|
|
33
|
+
canisterName: "my_canister",
|
|
34
|
+
method: listItems,
|
|
35
|
+
type: "suspenseInfiniteQuery",
|
|
36
|
+
}
|
|
37
|
+
const result = generateInfiniteQueryHook(options)
|
|
38
|
+
expect(result).toContain(
|
|
39
|
+
"export const useListItemsSuspenseInfiniteQuery = listItemsSuspenseInfiniteQuery.useInfiniteQuery"
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infinite Query hook template generator
|
|
3
|
+
*
|
|
4
|
+
* Generates createInfiniteQuery-based hooks for paginated canister methods.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MethodInfo, HookType } from "../types.js"
|
|
8
|
+
import {
|
|
9
|
+
getReactorName,
|
|
10
|
+
getServiceTypeName,
|
|
11
|
+
getHookExportName,
|
|
12
|
+
getReactHookName,
|
|
13
|
+
} from "../naming.js"
|
|
14
|
+
|
|
15
|
+
export interface InfiniteQueryHookOptions {
|
|
16
|
+
canisterName: string
|
|
17
|
+
method: MethodInfo
|
|
18
|
+
type?: HookType
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate an infinite query hook file content
|
|
23
|
+
*/
|
|
24
|
+
export function generateInfiniteQueryHook(
|
|
25
|
+
options: InfiniteQueryHookOptions
|
|
26
|
+
): string {
|
|
27
|
+
const { canisterName, method, type = "infiniteQuery" } = options
|
|
28
|
+
|
|
29
|
+
const reactorName = getReactorName(canisterName)
|
|
30
|
+
const serviceName = getServiceTypeName(canisterName)
|
|
31
|
+
const hookExportName = getHookExportName(method.name, type)
|
|
32
|
+
const reactHookName = getReactHookName(method.name, type)
|
|
33
|
+
|
|
34
|
+
return `/**
|
|
35
|
+
* Infinite Query: ${method.name}
|
|
36
|
+
*
|
|
37
|
+
* Auto-generated by @ic-reactor/codegen
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ CUSTOMIZATION REQUIRED: Configure getArgs and getNextPageParam below.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const { data, fetchNextPage, hasNextPage } = ${hookExportName}.useInfiniteQuery()
|
|
43
|
+
* const allItems = data?.pages.flatMap(page => page.items) ?? []
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { createInfiniteQuery } from "@ic-reactor/react"
|
|
47
|
+
import { ${reactorName}, type ${serviceName} } from "../reactor"
|
|
48
|
+
|
|
49
|
+
/** Define your pagination cursor type */
|
|
50
|
+
type PageCursor = number
|
|
51
|
+
|
|
52
|
+
export const ${hookExportName} = createInfiniteQuery(${reactorName}, {
|
|
53
|
+
functionName: "${method.name}",
|
|
54
|
+
|
|
55
|
+
initialPageParam: 0 as PageCursor,
|
|
56
|
+
|
|
57
|
+
/** Convert page param to method arguments — customize for your API */
|
|
58
|
+
getArgs: (pageParam: PageCursor) => {
|
|
59
|
+
return [{ offset: pageParam, limit: 10 }] as Parameters<${serviceName}["${method.name}"]>
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/** Extract next page param — return undefined when no more pages */
|
|
63
|
+
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
|
64
|
+
// Example: offset-based
|
|
65
|
+
// if (lastPage.items.length < 10) return undefined
|
|
66
|
+
// return lastPageParam + 10
|
|
67
|
+
return undefined
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/** React hook for paginated ${method.name} */
|
|
72
|
+
export const ${reactHookName} = ${hookExportName}.useInfiniteQuery
|
|
73
|
+
`
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { generateMutationHook, MutationHookOptions } from "./mutation"
|
|
3
|
+
import type { MethodInfo } from "../types"
|
|
4
|
+
|
|
5
|
+
describe("Mutation Hook Generation", () => {
|
|
6
|
+
const methodWithArgs: MethodInfo = {
|
|
7
|
+
name: "create_item",
|
|
8
|
+
type: "mutation",
|
|
9
|
+
hasArgs: true,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const methodNoArgs: MethodInfo = {
|
|
13
|
+
name: "init_system",
|
|
14
|
+
type: "mutation",
|
|
15
|
+
hasArgs: false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it("generates hook correctly for mutation with args", () => {
|
|
19
|
+
const options: MutationHookOptions = {
|
|
20
|
+
canisterName: "my_canister",
|
|
21
|
+
method: methodWithArgs,
|
|
22
|
+
}
|
|
23
|
+
const result = generateMutationHook(options)
|
|
24
|
+
expect(result).toMatchSnapshot()
|
|
25
|
+
expect(result).toContain("createMutation(myCanisterReactor, {")
|
|
26
|
+
expect(result).toContain(
|
|
27
|
+
"export const useCreateItemMutation = createItemMutation.useMutation"
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("generates hook correctly for mutation without args", () => {
|
|
32
|
+
const options: MutationHookOptions = {
|
|
33
|
+
canisterName: "my_canister",
|
|
34
|
+
method: methodNoArgs,
|
|
35
|
+
}
|
|
36
|
+
const result = generateMutationHook(options)
|
|
37
|
+
expect(result).toMatchSnapshot()
|
|
38
|
+
expect(result).toContain("createMutation(myCanisterReactor, {")
|
|
39
|
+
expect(result).toContain(
|
|
40
|
+
"export const executeInitSystem = initSystemMutation.execute"
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation hook template generator
|
|
3
|
+
*
|
|
4
|
+
* Generates createMutation-based hooks for canister update methods.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MethodInfo } from "../types.js"
|
|
8
|
+
import { toPascalCase, getHookExportName, getReactorName } from "../naming.js"
|
|
9
|
+
|
|
10
|
+
export interface MutationHookOptions {
|
|
11
|
+
canisterName: string
|
|
12
|
+
method: MethodInfo
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a mutation hook file content
|
|
17
|
+
*/
|
|
18
|
+
export function generateMutationHook(options: MutationHookOptions): string {
|
|
19
|
+
const { canisterName, method } = options
|
|
20
|
+
|
|
21
|
+
const pascalMethod = toPascalCase(method.name)
|
|
22
|
+
const reactorName = getReactorName(canisterName)
|
|
23
|
+
const hookExportName = getHookExportName(method.name, "mutation")
|
|
24
|
+
|
|
25
|
+
return `/**
|
|
26
|
+
* Mutation: ${method.name}
|
|
27
|
+
*
|
|
28
|
+
* Auto-generated by @ic-reactor/codegen
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const { mutate, isPending } = ${hookExportName}.useMutation()
|
|
32
|
+
* mutate(${method.hasArgs ? "[arg1, arg2]" : "[]"})
|
|
33
|
+
*
|
|
34
|
+
* // Direct execution (outside React)
|
|
35
|
+
* const result = await ${hookExportName}.execute(${method.hasArgs ? "[arg1, arg2]" : "[]"})
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { createMutation } from "@ic-reactor/react"
|
|
39
|
+
import { ${reactorName} } from "../reactor"
|
|
40
|
+
|
|
41
|
+
export const ${hookExportName} = createMutation(${reactorName}, {
|
|
42
|
+
functionName: "${method.name}",
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** React hook for ${method.name} */
|
|
46
|
+
export const use${pascalMethod}Mutation = ${hookExportName}.useMutation
|
|
47
|
+
|
|
48
|
+
/** Execute ${method.name} directly (outside React) */
|
|
49
|
+
export const execute${pascalMethod} = ${hookExportName}.execute
|
|
50
|
+
`
|
|
51
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { generateQueryHook, QueryHookOptions } from "./query"
|
|
3
|
+
import type { MethodInfo } from "../types"
|
|
4
|
+
|
|
5
|
+
describe("Query Hook Generation", () => {
|
|
6
|
+
const methodWithArgs: MethodInfo = {
|
|
7
|
+
name: "get_user",
|
|
8
|
+
type: "query",
|
|
9
|
+
hasArgs: true,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const methodNoArgs: MethodInfo = {
|
|
13
|
+
name: "list_items",
|
|
14
|
+
type: "query",
|
|
15
|
+
hasArgs: false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it("generates hook for query with arguments (factory)", () => {
|
|
19
|
+
const options: QueryHookOptions = {
|
|
20
|
+
canisterName: "my_canister",
|
|
21
|
+
method: methodWithArgs,
|
|
22
|
+
}
|
|
23
|
+
expect(generateQueryHook(options)).toMatchSnapshot()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it("generates hook for query without arguments (static)", () => {
|
|
27
|
+
const options: QueryHookOptions = {
|
|
28
|
+
canisterName: "my_canister",
|
|
29
|
+
method: methodNoArgs,
|
|
30
|
+
}
|
|
31
|
+
expect(generateQueryHook(options)).toMatchSnapshot()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("generates suspense hooks correctly", () => {
|
|
35
|
+
const options: QueryHookOptions = {
|
|
36
|
+
canisterName: "my_canister",
|
|
37
|
+
method: methodWithArgs,
|
|
38
|
+
type: "suspenseQuery",
|
|
39
|
+
}
|
|
40
|
+
const result = generateQueryHook(options)
|
|
41
|
+
expect(result).toMatchSnapshot()
|
|
42
|
+
expect(result).toContain("createSuspenseQueryFactory")
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query hook template generator
|
|
3
|
+
*
|
|
4
|
+
* Generates createQuery/createQueryFactory-based hooks for canister query methods.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MethodInfo, HookType } from "../types.js"
|
|
8
|
+
import { getHookExportName, getReactorName } from "../naming.js"
|
|
9
|
+
|
|
10
|
+
export interface QueryHookOptions {
|
|
11
|
+
canisterName: string
|
|
12
|
+
method: MethodInfo
|
|
13
|
+
type?: HookType
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate a query hook file content
|
|
18
|
+
*/
|
|
19
|
+
export function generateQueryHook(options: QueryHookOptions): string {
|
|
20
|
+
const { canisterName, method, type = "query" } = options
|
|
21
|
+
|
|
22
|
+
const reactorName = getReactorName(canisterName)
|
|
23
|
+
const hookExportName = getHookExportName(method.name, type)
|
|
24
|
+
|
|
25
|
+
const isSuspense = type === "suspenseQuery"
|
|
26
|
+
const creatorFn = isSuspense ? "createSuspenseQuery" : "createQuery"
|
|
27
|
+
const factoryFn = isSuspense
|
|
28
|
+
? "createSuspenseQueryFactory"
|
|
29
|
+
: "createQueryFactory"
|
|
30
|
+
const hookName = isSuspense ? "useSuspenseQuery" : "useQuery"
|
|
31
|
+
|
|
32
|
+
if (method.hasArgs) {
|
|
33
|
+
return `/**
|
|
34
|
+
* Query Factory: ${method.name}
|
|
35
|
+
*
|
|
36
|
+
* Auto-generated by @ic-reactor/codegen
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const { data } = ${hookExportName}([arg1, arg2]).${hookName}()
|
|
40
|
+
* const data = await ${hookExportName}([arg1, arg2]).fetch()
|
|
41
|
+
* ${hookExportName}([arg1, arg2]).invalidate()
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { ${factoryFn} } from "@ic-reactor/react"
|
|
45
|
+
import { ${reactorName} } from "../reactor"
|
|
46
|
+
|
|
47
|
+
export const ${hookExportName} = ${factoryFn}(${reactorName}, {
|
|
48
|
+
functionName: "${method.name}",
|
|
49
|
+
})
|
|
50
|
+
`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return `/**
|
|
54
|
+
* Query: ${method.name}
|
|
55
|
+
*
|
|
56
|
+
* Auto-generated by @ic-reactor/codegen
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const { data } = ${hookExportName}.${hookName}()
|
|
60
|
+
* const data = await ${hookExportName}.fetch()
|
|
61
|
+
* ${hookExportName}.invalidate()
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
import { ${creatorFn} } from "@ic-reactor/react"
|
|
65
|
+
import { ${reactorName} } from "../reactor"
|
|
66
|
+
|
|
67
|
+
export const ${hookExportName} = ${creatorFn}(${reactorName}, {
|
|
68
|
+
functionName: "${method.name}",
|
|
69
|
+
})
|
|
70
|
+
`
|
|
71
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { generateReactorFile } from "./reactor"
|
|
3
|
+
import type { ReactorGeneratorOptions } from "../types"
|
|
4
|
+
|
|
5
|
+
describe("Reactor File Generation", () => {
|
|
6
|
+
const baseOptions: ReactorGeneratorOptions = {
|
|
7
|
+
canisterName: "my_canister",
|
|
8
|
+
canisterConfig: {
|
|
9
|
+
didFile: "path/to/my_canister.did",
|
|
10
|
+
outDir: "src/declarations/my_canister",
|
|
11
|
+
clientManagerPath: "../../client",
|
|
12
|
+
},
|
|
13
|
+
hasDeclarations: true,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("Simple Mode", () => {
|
|
17
|
+
it("generates correctly", () => {
|
|
18
|
+
const result = generateReactorFile(baseOptions)
|
|
19
|
+
expect(result).toMatchSnapshot()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("uses default client path if not provided", () => {
|
|
23
|
+
const options = { ...baseOptions, canisterConfig: { didFile: "foo.did" } }
|
|
24
|
+
const result = generateReactorFile(options)
|
|
25
|
+
expect(result).toContain(
|
|
26
|
+
'import { clientManager } from "../../lib/client"'
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("Advanced Mode", () => {
|
|
32
|
+
it("generates per-method hooks correctly", () => {
|
|
33
|
+
const didContent = `service : {
|
|
34
|
+
get_user: (nat) -> (opt User) query;
|
|
35
|
+
list_items: () -> (vec Item) query;
|
|
36
|
+
create_item: (Item) -> (Result);
|
|
37
|
+
update_status: () -> (Result); // No args mutation
|
|
38
|
+
}`
|
|
39
|
+
|
|
40
|
+
const options: ReactorGeneratorOptions = {
|
|
41
|
+
...baseOptions,
|
|
42
|
+
advanced: true,
|
|
43
|
+
didContent,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = generateReactorFile(options)
|
|
47
|
+
expect(result).toMatchSnapshot()
|
|
48
|
+
// Should include static query creation for no-arg method
|
|
49
|
+
expect(result).toContain(
|
|
50
|
+
"export const listItemsQuery = createQuery(myCanisterReactor, {"
|
|
51
|
+
)
|
|
52
|
+
// Should handle mutation
|
|
53
|
+
expect(result).toContain(
|
|
54
|
+
"export const updateStatusMutation = createMutation(myCanisterReactor, {"
|
|
55
|
+
)
|
|
56
|
+
// Should NOT handle methods with args
|
|
57
|
+
expect(result).not.toContain("export const getUserQuery = createQuery")
|
|
58
|
+
expect(result).not.toContain(
|
|
59
|
+
"export const createItemMutation = createMutation"
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe("Fallback Mode", () => {
|
|
65
|
+
it("generates correctly without declarations", () => {
|
|
66
|
+
const options: ReactorGeneratorOptions = {
|
|
67
|
+
...baseOptions,
|
|
68
|
+
hasDeclarations: false,
|
|
69
|
+
}
|
|
70
|
+
const result = generateReactorFile(options)
|
|
71
|
+
expect(result).toMatchSnapshot()
|
|
72
|
+
|
|
73
|
+
expect(result).toContain(
|
|
74
|
+
"type MyCanisterService = Record<string, (...args: unknown[]) => Promise<unknown>>"
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|