@ic-reactor/codegen 0.2.0 → 0.3.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/src/bindgen.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Bindgen utilities
3
3
  *
4
- * Generates TypeScript declarations from Candid files using @icp-sdk/bindgen.
4
+ * Generates TypeScript declarations from Candid files using @ic-reactor/parser.
5
5
  */
6
6
 
7
- import { generate } from "@icp-sdk/bindgen/core"
7
+ import { didToJs, didToTs } from "@ic-reactor/parser"
8
8
  import path from "node:path"
9
9
  import fs from "node:fs"
10
10
 
@@ -27,7 +27,8 @@ export interface BindgenResult {
27
27
  * Generate TypeScript declarations from a Candid file
28
28
  *
29
29
  * This creates:
30
- * - declarations/<canisterName>.did.ts - IDL factory and types
30
+ * - declarations/<canisterName>.js - IDL factory
31
+ * - declarations/<canisterName>.d.ts - Types
31
32
  */
32
33
  export async function generateDeclarations(
33
34
  options: BindgenOptions
@@ -44,8 +45,12 @@ export async function generateDeclarations(
44
45
  }
45
46
 
46
47
  const declarationsDir = path.join(outDir, "declarations")
48
+ const didFileName = path.basename(didFile) // e.g., "my_canister.did"
47
49
 
48
50
  try {
51
+ // Read content first so we can safely delete the directory if didFile is inside it
52
+ const didContent = fs.readFileSync(didFile, "utf-8")
53
+
49
54
  // Ensure the output directory exists
50
55
  if (!fs.existsSync(outDir)) {
51
56
  fs.mkdirSync(outDir, { recursive: true })
@@ -57,17 +62,18 @@ export async function generateDeclarations(
57
62
  }
58
63
  fs.mkdirSync(declarationsDir, { recursive: true })
59
64
 
60
- // Note: bindgen appends "declarations" internally, so we pass the parent directory
61
- await generate({
62
- didFile,
63
- outDir, // Pass the parent directory; bindgen appends "declarations"
64
- output: {
65
- actor: {
66
- disabled: true, // We don't need actor creation, we use Reactor
67
- },
68
- force: true, // Overwrite existing files
69
- },
70
- })
65
+ const jsContent = didToJs(didContent)
66
+ const tsContent = didToTs(didContent)
67
+
68
+ // Write .did, .js and .d.ts
69
+ const baseName = didFileName.replace(/\.did$/, "")
70
+ const jsPath = path.join(declarationsDir, baseName + ".js")
71
+ const dtsPath = path.join(declarationsDir, baseName + ".d.ts")
72
+ const didPath = path.join(declarationsDir, didFileName)
73
+
74
+ fs.writeFileSync(jsPath, jsContent)
75
+ fs.writeFileSync(dtsPath, tsContent)
76
+ fs.writeFileSync(didPath, didContent)
71
77
 
72
78
  return {
73
79
  success: true,
@@ -90,24 +96,6 @@ export function declarationsExist(
90
96
  canisterName: string
91
97
  ): boolean {
92
98
  const declarationsDir = path.join(outDir, "declarations")
93
- const didTsPath = path.join(declarationsDir, `${canisterName}.did.ts`)
99
+ const didTsPath = path.join(declarationsDir, `${canisterName}.d.ts`)
94
100
  return fs.existsSync(didTsPath)
95
101
  }
96
-
97
- /**
98
- * Save a Candid source to a file (for use with fetch command)
99
- */
100
- export function saveCandidFile(
101
- candidSource: string,
102
- outDir: string,
103
- canisterName: string
104
- ): string {
105
- const candidDir = path.join(outDir, "candid")
106
- if (!fs.existsSync(candidDir)) {
107
- fs.mkdirSync(candidDir, { recursive: true })
108
- }
109
-
110
- const candidPath = path.join(candidDir, `${canisterName}.did`)
111
- fs.writeFileSync(candidPath, candidSource)
112
- return candidPath
113
- }
package/src/index.ts CHANGED
@@ -5,47 +5,16 @@
5
5
  * Used by both @ic-reactor/vite-plugin and @ic-reactor/cli.
6
6
  */
7
7
 
8
- // Types
9
- export type {
10
- MethodInfo,
11
- CanisterConfig,
12
- HookType,
13
- GeneratorOptions,
14
- ReactorGeneratorOptions,
15
- } from "./types.js"
16
-
17
8
  // Naming utilities
18
- export {
19
- toPascalCase,
20
- toCamelCase,
21
- getHookFileName,
22
- getHookExportName,
23
- getReactHookName,
24
- getReactorName,
25
- getServiceTypeName,
26
- } from "./naming.js"
27
-
28
- // DID parsing
29
- export {
30
- parseDIDFile,
31
- extractMethods,
32
- getMethodsByType,
33
- formatMethodForDisplay,
34
- } from "./did.js"
9
+ export { toPascalCase, getReactorName, getServiceTypeName } from "./naming.js"
35
10
 
36
11
  // Bindgen utilities
37
- export {
38
- generateDeclarations,
39
- declarationsExist,
40
- saveCandidFile,
41
- } from "./bindgen.js"
12
+ export { generateDeclarations, declarationsExist } from "./bindgen.js"
42
13
  export type { BindgenOptions, BindgenResult } from "./bindgen.js"
43
14
 
44
15
  // Template generators
45
16
  export { generateReactorFile } from "./templates/reactor.js"
46
- export { generateQueryHook } from "./templates/query.js"
47
- export type { QueryHookOptions } from "./templates/query.js"
48
- export { generateMutationHook } from "./templates/mutation.js"
49
- export type { MutationHookOptions } from "./templates/mutation.js"
50
- export { generateInfiniteQueryHook } from "./templates/infiniteQuery.js"
51
- export type { InfiniteQueryHookOptions } from "./templates/infiniteQuery.js"
17
+ export type { ReactorGeneratorOptions } from "./templates/reactor.js"
18
+
19
+ export { generateClientFile } from "./templates/client.js"
20
+ export type { ClientGeneratorOptions } from "./templates/client.js"
@@ -0,0 +1,35 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`Client File Generation > generates correctly with custom query client 1`] = `
4
+ "import { ClientManager } from "@ic-reactor/react"
5
+ import { queryClient } from "../query-client"
6
+
7
+ /**
8
+ * IC Reactor Client Manager
9
+ *
10
+ * Auto-generated by @ic-reactor/codegen
11
+ */
12
+ export const clientManager = new ClientManager({
13
+ queryClient,
14
+ withCanisterEnv: true,
15
+ })
16
+ "
17
+ `;
18
+
19
+ exports[`Client File Generation > generates correctly with default host 1`] = `
20
+ "import { ClientManager } from "@ic-reactor/react"
21
+ import { QueryClient } from "@tanstack/react-query"
22
+
23
+ export const queryClient = new QueryClient()
24
+
25
+ /**
26
+ * IC Reactor Client Manager
27
+ *
28
+ * Auto-generated by @ic-reactor/codegen
29
+ */
30
+ export const clientManager = new ClientManager({
31
+ queryClient,
32
+ withCanisterEnv: true,
33
+ })
34
+ "
35
+ `;
@@ -1,138 +1,14 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
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"
3
+ exports[`Reactor File Generation > Standard Mode > generates correctly 1`] = `
4
+ "import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
128
5
  import { clientManager } from "../../client"
129
- import { idlFactory, type _SERVICE } from "./declarations/my_canister.did"
6
+ import { idlFactory, type _SERVICE } from "./declarations/my_canister"
130
7
 
131
8
  export type MyCanisterService = _SERVICE
132
9
 
133
10
  /**
134
- * MyCanister Reactor — Display mode.
135
- * Automatically converts bigint → string, Principal → string, etc.
11
+ * MyCanister Display Reactor
136
12
  */
137
13
  export const myCanisterReactor = new DisplayReactor<MyCanisterService>({
138
14
  clientManager,
@@ -140,7 +16,7 @@ export const myCanisterReactor = new DisplayReactor<MyCanisterService>({
140
16
  name: "my_canister",
141
17
  })
142
18
 
143
- const {
19
+ export const {
144
20
  useActorQuery: useMyCanisterQuery,
145
21
  useActorSuspenseQuery: useMyCanisterSuspenseQuery,
146
22
  useActorInfiniteQuery: useMyCanisterInfiniteQuery,
@@ -148,16 +24,5 @@ const {
148
24
  useActorMutation: useMyCanisterMutation,
149
25
  useActorMethod: useMyCanisterMethod,
150
26
  } = createActorHooks(myCanisterReactor)
151
-
152
- export {
153
- useMyCanisterQuery,
154
- useMyCanisterSuspenseQuery,
155
- useMyCanisterInfiniteQuery,
156
- useMyCanisterSuspenseInfiniteQuery,
157
- useMyCanisterMutation,
158
- useMyCanisterMethod,
159
- }
160
-
161
- export { idlFactory }
162
27
  "
163
28
  `;
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { generateClientFile } from "./client"
3
+
4
+ describe("Client File Generation", () => {
5
+ it("generates correctly with default host", () => {
6
+ const result = generateClientFile()
7
+ expect(result).toMatchSnapshot()
8
+ expect(result).toContain(
9
+ 'import { QueryClient } from "@tanstack/react-query'
10
+ )
11
+ })
12
+
13
+ it("generates correctly with custom query client", () => {
14
+ const result = generateClientFile({
15
+ queryClientPath: "../query-client",
16
+ })
17
+ expect(result).toMatchSnapshot()
18
+ expect(result).toContain('import { queryClient } from "../query-client"')
19
+ })
20
+ })
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Client Manager Generator
3
+ */
4
+
5
+ export interface ClientGeneratorOptions {
6
+ /** Optional path to an existing queryClient */
7
+ queryClientPath?: string
8
+ }
9
+
10
+ /**
11
+ * Generate a central client manager file
12
+ */
13
+ export function generateClientFile(
14
+ options: ClientGeneratorOptions = {}
15
+ ): string {
16
+ const { queryClientPath } = options
17
+
18
+ return `import { ClientManager } from "@ic-reactor/react"
19
+ ${
20
+ queryClientPath
21
+ ? `import { queryClient } from "${queryClientPath}"`
22
+ : `import { QueryClient } from "@tanstack/react-query"
23
+
24
+ export const queryClient = new QueryClient()`
25
+ }
26
+
27
+ /**
28
+ * IC Reactor Client Manager
29
+ *
30
+ * Auto-generated by @ic-reactor/codegen
31
+ */
32
+ export const clientManager = new ClientManager({
33
+ queryClient,
34
+ withCanisterEnv: true,
35
+ })
36
+ `
37
+ }
@@ -1,19 +1,14 @@
1
1
  import { describe, it, expect } from "vitest"
2
- import { generateReactorFile } from "./reactor"
3
- import type { ReactorGeneratorOptions } from "../types"
2
+ import { generateReactorFile, type ReactorGeneratorOptions } from "./reactor"
4
3
 
5
4
  describe("Reactor File Generation", () => {
6
5
  const baseOptions: ReactorGeneratorOptions = {
7
6
  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,
7
+ didFile: "path/to/my_canister.did",
8
+ clientManagerPath: "../../client",
14
9
  }
15
10
 
16
- describe("Simple Mode", () => {
11
+ describe("Standard Mode", () => {
17
12
  it("generates correctly", () => {
18
13
  const result = generateReactorFile(baseOptions)
19
14
  expect(result).toMatchSnapshot()
@@ -22,57 +17,7 @@ describe("Reactor File Generation", () => {
22
17
  it("uses default client path if not provided", () => {
23
18
  const options = { ...baseOptions, canisterConfig: { didFile: "foo.did" } }
24
19
  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
- )
20
+ expect(result).toContain('import { clientManager } from "../../client"')
76
21
  })
77
22
  })
78
23
  })