@ic-reactor/codegen 0.4.1 → 0.5.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.d.ts CHANGED
@@ -1,83 +1,249 @@
1
+ /**
2
+ * @ic-reactor/codegen — Core Types
3
+ *
4
+ * All shared types used across generators, pipeline, CLI, and vite-plugin.
5
+ */
6
+ /**
7
+ * Per-canister configuration.
8
+ * `name` is always required — it drives all naming in generators.
9
+ */
10
+ interface CanisterConfig {
11
+ /** Canister name (used for variable names, file names, etc.) */
12
+ name: string;
13
+ /** Path to the .did file (relative to project root, or absolute) */
14
+ didFile: string;
15
+ /** Override output dir for this specific canister */
16
+ outDir?: string;
17
+ /**
18
+ * Import path to the ClientManager (relative from the generated reactor file).
19
+ * Example: "../../clients" → `import { clientManager } from "../../clients"`
20
+ */
21
+ clientManagerPath?: string;
22
+ /** Optional fixed canister ID */
23
+ canisterId?: string;
24
+ }
25
+ /**
26
+ * Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
27
+ */
28
+ interface CodegenConfig {
29
+ /** JSON Schema reference */
30
+ $schema?: string;
31
+ /**
32
+ * Default output directory for all canisters (relative to project root).
33
+ * Individual canisters can override via `CanisterConfig.outDir`.
34
+ */
35
+ outDir: string;
36
+ /**
37
+ * Default import path for the ClientManager (relative from generated files).
38
+ * Individual canisters can override via `CanisterConfig.clientManagerPath`.
39
+ */
40
+ clientManagerPath?: string;
41
+ /** Canister configurations, keyed by canister name */
42
+ canisters: Record<string, CanisterConfig>;
43
+ }
44
+ /**
45
+ * Result of a single file-writing generator step.
46
+ */
47
+ interface GeneratorResult {
48
+ success: boolean;
49
+ /** Absolute path of the file that was (or would have been) written */
50
+ filePath: string;
51
+ /** If true, skipped because the file already existed */
52
+ skipped?: boolean;
53
+ error?: string;
54
+ }
55
+
56
+ /**
57
+ * Codegen Pipeline
58
+ *
59
+ * Orchestrates all generators for a single canister.
60
+ * This is the primary entry point used by @ic-reactor/cli and @ic-reactor/vite-plugin.
61
+ *
62
+ * Pipeline steps (in order):
63
+ * 1. Resolve paths (didFile, outDir)
64
+ * 2. Generate declarations (JS + .d.ts + .did copy)
65
+ * 3. Generate reactor file (index.ts)
66
+ */
67
+
68
+ interface PipelineOptions {
69
+ /** Canister name and config */
70
+ canisterConfig: CanisterConfig;
71
+ /**
72
+ * Absolute path to the project root.
73
+ * Relative paths in `CanisterConfig` are resolved from here.
74
+ */
75
+ projectRoot: string;
76
+ /**
77
+ * Global codegen config (for fallback outDir and clientManagerPath).
78
+ */
79
+ globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
80
+ }
81
+ interface PipelineResult {
82
+ canisterName: string;
83
+ success: boolean;
84
+ /** All files the pipeline attempted to write */
85
+ files: GeneratorResult[];
86
+ error?: string;
87
+ }
88
+ /**
89
+ * Run the full codegen pipeline for a single canister.
90
+ *
91
+ * @returns PipelineResult with per-file details
92
+ */
93
+ declare function runCanisterPipeline(options: PipelineOptions): Promise<PipelineResult>;
94
+
1
95
  /**
2
96
  * Naming utilities for code generation
3
97
  *
4
- * Uses the `change-case` library for base transformations,
5
- * with domain-specific helpers for IC Reactor patterns.
98
+ * Pure functions that convert canister/method names to correctly cased
99
+ * identifiers used throughout generated code.
6
100
  */
7
101
  /**
8
- * Convert string to PascalCase
102
+ * Convert string to PascalCase.
9
103
  * @example toPascalCase("get_message") → "GetMessage"
10
104
  * @example toPascalCase("my-canister") → "MyCanister"
11
105
  */
12
106
  declare function toPascalCase(str: string): string;
13
107
  /**
14
- * Generate reactor variable name
108
+ * Generate the reactor variable name for a canister.
15
109
  * @example getReactorName("backend") → "backendReactor"
110
+ * @example getReactorName("my_canister") → "myCanisterReactor"
16
111
  */
17
112
  declare function getReactorName(canisterName: string): string;
18
113
  /**
19
- * Generate service type name
114
+ * Generate the service type name for a canister.
20
115
  * @example getServiceTypeName("backend") → "BackendService"
116
+ * @example getServiceTypeName("my_canister") → "MyCanisterService"
21
117
  */
22
118
  declare function getServiceTypeName(canisterName: string): string;
23
119
 
24
120
  /**
25
- * Bindgen utilities
121
+ * Candid Parser Utilities
122
+ *
123
+ * Parses Candid .did files to extract service method signatures.
124
+ * Used by the CLI for listing methods and by advanced generators.
125
+ */
126
+ type MethodType = "query" | "mutation";
127
+ interface MethodInfo {
128
+ /** Method name as it appears in the Candid service */
129
+ name: string;
130
+ /** "query" for read-only calls, "mutation" for update calls */
131
+ type: MethodType;
132
+ /** True if the method takes at least one argument */
133
+ hasArgs: boolean;
134
+ }
135
+ /**
136
+ * Extract method information from raw Candid source text.
26
137
  *
27
- * Generates TypeScript declarations from Candid files using @ic-reactor/parser.
138
+ * Compiles the Candid to JS and inspects the IDL.Service definition.
28
139
  */
29
- interface BindgenOptions {
30
- /** Path to the .did file */
140
+ declare function extractMethods(didContent: string): MethodInfo[];
141
+ /**
142
+ * Parse a .did file from disk and return its methods.
143
+ *
144
+ * @throws if the file does not exist or fails to parse
145
+ */
146
+ declare function parseDIDFile(didFilePath: string): MethodInfo[];
147
+
148
+ /**
149
+ * Declarations Generator
150
+ *
151
+ * Generates TypeScript declaration files (.js IDL factory + .d.ts types)
152
+ * from a Candid .did file using @ic-reactor/parser.
153
+ *
154
+ * Output structure:
155
+ * <outDir>/declarations/<name>.js — IDL factory
156
+ * <outDir>/declarations/<name>.d.ts — TypeScript types
157
+ * <outDir>/declarations/<name>.did — Copy of the source .did file
158
+ */
159
+
160
+ interface DeclarationsGeneratorOptions {
161
+ /** Absolute path to the .did file */
31
162
  didFile: string;
32
- /** Output directory for generated declarations */
163
+ /** Absolute path to the output directory (declarations/ will be created inside) */
33
164
  outDir: string;
34
- /** Canister name (used for naming) */
165
+ /** Canister name (used only for error messages) */
35
166
  canisterName: string;
36
167
  }
37
- interface BindgenResult {
168
+ interface DeclarationsGeneratorResult {
38
169
  success: boolean;
39
170
  declarationsDir: string;
171
+ files: GeneratorResult[];
40
172
  error?: string;
41
173
  }
42
174
  /**
43
- * Generate TypeScript declarations from a Candid file
175
+ * Generate TypeScript declarations from a Candid file.
44
176
  *
45
- * This creates:
46
- * - declarations/<canisterName>.js - IDL factory
47
- * - declarations/<canisterName>.d.ts - Types
177
+ * Always cleans and regenerates the declarations directory to ensure
178
+ * it's in sync with the source .did file.
48
179
  */
49
- declare function generateDeclarations(options: BindgenOptions): Promise<BindgenResult>;
180
+ declare function generateDeclarations(options: DeclarationsGeneratorOptions): Promise<DeclarationsGeneratorResult>;
50
181
  /**
51
- * Check if declarations already exist for a canister
182
+ * Check if declarations already exist for a canister.
52
183
  */
53
184
  declare function declarationsExist(outDir: string, canisterName: string): boolean;
54
185
 
55
186
  /**
56
- * Reactor file template generator
187
+ * Reactor File Generator
188
+ *
189
+ * Generates the main `index.ts` for a canister — a DisplayReactor instance
190
+ * plus the full set of typed hooks via `createActorHooks`.
191
+ *
192
+ * Generated output example (for canister "backend"):
57
193
  *
58
- * Generates the reactor instance file for a canister using DisplayReactor.
59
- * Standardizes the output to include typed hooks and clean imports.
194
+ * import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
195
+ * import { clientManager } from "../../clients"
196
+ * import { idlFactory, type _SERVICE } from "./declarations/backend"
197
+ *
198
+ * export type BackendService = _SERVICE
199
+ *
200
+ * export const backendReactor = new DisplayReactor<BackendService>({ ... })
201
+ *
202
+ * export const {
203
+ * useActorQuery: useBackendQuery,
204
+ * ...
205
+ * } = createActorHooks(backendReactor)
60
206
  */
61
- type ReactorGeneratorOptions = {
207
+ interface ReactorGeneratorOptions {
208
+ /** Canister name (e.g. "backend") */
62
209
  canisterName: string;
210
+ /**
211
+ * Path to the .did file. Used to derive the declarations import path.
212
+ * Can be relative or absolute — only the basename is used.
213
+ */
63
214
  didFile: string;
215
+ /**
216
+ * Import path for the client manager, relative from the generated reactor file.
217
+ * Default: "../../clients"
218
+ */
64
219
  clientManagerPath?: string;
65
- };
220
+ }
66
221
  /**
67
- * Generate the reactor file content
222
+ * Generate the content of a canister's `index.ts` reactor file.
68
223
  */
69
224
  declare function generateReactorFile(options: ReactorGeneratorOptions): string;
70
225
 
71
226
  /**
72
227
  * Client Manager Generator
228
+ *
229
+ * Generates a central `clients.ts` file that creates a `ClientManager` instance.
230
+ * This file is shared across all canisters in a project.
231
+ *
232
+ * Users typically customize this file once to configure:
233
+ * - Custom host (local vs. mainnet)
234
+ * - Identity provider
235
+ * - Query client options
73
236
  */
74
237
  interface ClientGeneratorOptions {
75
- /** Optional path to an existing queryClient */
238
+ /**
239
+ * Import path to an existing `queryClient` instance.
240
+ * If omitted, a default `QueryClient` will be created.
241
+ */
76
242
  queryClientPath?: string;
77
243
  }
78
244
  /**
79
- * Generate a central client manager file
245
+ * Generate the content for a central client manager file (`clients.ts`).
80
246
  */
81
247
  declare function generateClientFile(options?: ClientGeneratorOptions): string;
82
248
 
83
- export { type BindgenOptions, type BindgenResult, type ClientGeneratorOptions, type ReactorGeneratorOptions, declarationsExist, generateClientFile, generateDeclarations, generateReactorFile, getReactorName, getServiceTypeName, toPascalCase };
249
+ export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
package/dist/index.js CHANGED
@@ -1,33 +1,23 @@
1
- // src/naming.ts
2
- import { camelCase, pascalCase } from "change-case";
3
- function toPascalCase(str) {
4
- return pascalCase(str);
5
- }
6
- function toCamelCase(str) {
7
- return camelCase(str);
8
- }
9
- function getReactorName(canisterName) {
10
- return `${toCamelCase(canisterName)}Reactor`;
11
- }
12
- function getServiceTypeName(canisterName) {
13
- return `${toPascalCase(canisterName)}Service`;
14
- }
1
+ // src/pipeline.ts
2
+ import fs2 from "fs";
3
+ import path3 from "path";
15
4
 
16
- // src/bindgen.ts
5
+ // src/generators/declarations.ts
17
6
  import { didToJs, didToTs } from "@ic-reactor/parser";
18
7
  import path from "path";
19
8
  import fs from "fs";
20
9
  async function generateDeclarations(options) {
21
- const { didFile, outDir } = options;
10
+ const { didFile, outDir, canisterName } = options;
22
11
  if (!fs.existsSync(didFile)) {
23
12
  return {
24
13
  success: false,
25
14
  declarationsDir: "",
15
+ files: [],
26
16
  error: `DID file not found: ${didFile}`
27
17
  };
28
18
  }
29
19
  const declarationsDir = path.join(outDir, "declarations");
30
- const didFileName = path.basename(didFile);
20
+ const baseName = path.basename(didFile, ".did");
31
21
  try {
32
22
  const didContent = fs.readFileSync(didFile, "utf-8");
33
23
  if (!fs.existsSync(outDir)) {
@@ -39,73 +29,75 @@ async function generateDeclarations(options) {
39
29
  fs.mkdirSync(declarationsDir, { recursive: true });
40
30
  const jsContent = didToJs(didContent);
41
31
  const tsContent = didToTs(didContent);
42
- const baseName = didFileName.replace(/\.did$/, "");
43
- const jsPath = path.join(declarationsDir, baseName + ".js");
44
- const dtsPath = path.join(declarationsDir, baseName + ".d.ts");
45
- const didPath = path.join(declarationsDir, didFileName);
32
+ const jsPath = path.join(declarationsDir, `${baseName}.js`);
33
+ const dtsPath = path.join(declarationsDir, `${baseName}.d.ts`);
34
+ const didCopyPath = path.join(declarationsDir, `${baseName}.did`);
46
35
  fs.writeFileSync(jsPath, jsContent);
47
36
  fs.writeFileSync(dtsPath, tsContent);
48
- fs.writeFileSync(didPath, didContent);
37
+ fs.writeFileSync(didCopyPath, didContent);
49
38
  return {
50
39
  success: true,
51
- declarationsDir
40
+ declarationsDir,
41
+ files: [
42
+ { success: true, filePath: jsPath },
43
+ { success: true, filePath: dtsPath },
44
+ { success: true, filePath: didCopyPath }
45
+ ]
52
46
  };
53
47
  } catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
54
49
  return {
55
50
  success: false,
56
51
  declarationsDir,
57
- error: error instanceof Error ? error.message : String(error)
52
+ files: [],
53
+ error: `[${canisterName}] Failed to generate declarations: ${message}`
58
54
  };
59
55
  }
60
56
  }
61
57
  function declarationsExist(outDir, canisterName) {
62
- const declarationsDir = path.join(outDir, "declarations");
63
- const didTsPath = path.join(declarationsDir, `${canisterName}.d.ts`);
64
- return fs.existsSync(didTsPath);
58
+ const dtsPath = path.join(outDir, "declarations", `${canisterName}.d.ts`);
59
+ return fs.existsSync(dtsPath);
65
60
  }
66
61
 
67
- // src/templates/reactor.ts
62
+ // src/generators/reactor.ts
68
63
  import path2 from "path";
64
+
65
+ // src/naming.ts
66
+ import { camelCase, pascalCase } from "change-case";
67
+ function toPascalCase(str) {
68
+ return pascalCase(str);
69
+ }
70
+ function toCamelCase(str) {
71
+ return camelCase(str);
72
+ }
73
+ function getReactorName(canisterName) {
74
+ return `${toCamelCase(canisterName)}Reactor`;
75
+ }
76
+ function getServiceTypeName(canisterName) {
77
+ return `${toPascalCase(canisterName)}Service`;
78
+ }
79
+
80
+ // src/generators/reactor.ts
69
81
  function generateReactorFile(options) {
70
- const pascalName = toPascalCase(options.canisterName);
71
- const reactorName = getReactorName(options.canisterName);
72
- const serviceName = getServiceTypeName(options.canisterName);
73
- const reactorType = "DisplayReactor";
74
- const didFileName = path2.basename(options.didFile);
75
- const baseName = didFileName.replace(/\.did$/, "");
82
+ const { canisterName, didFile, clientManagerPath = "../../clients" } = options;
83
+ const pascalName = toPascalCase(canisterName);
84
+ const reactorName = getReactorName(canisterName);
85
+ const serviceName = getServiceTypeName(canisterName);
86
+ const baseName = path2.basename(didFile, ".did");
76
87
  const declarationsPath = `./declarations/${baseName}`;
77
- const clientManagerPath = options.clientManagerPath ?? "../../clients";
78
- const vars = {
79
- canisterName: options.canisterName,
80
- pascalName,
81
- reactorName,
82
- serviceName,
83
- reactorType,
84
- clientManagerPath,
85
- declarationsPath
86
- };
87
- return generateStandardReactorFile(vars);
88
- }
89
- function generateStandardReactorFile(vars) {
90
- const {
91
- pascalName,
92
- reactorName,
93
- serviceName,
94
- reactorType,
95
- clientManagerPath,
96
- declarationsPath,
97
- canisterName
98
- } = vars;
99
- return `import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
88
+ return `import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
100
89
  import { clientManager } from "${clientManagerPath}"
101
90
  import { idlFactory, type _SERVICE } from "${declarationsPath}"
102
91
 
103
92
  export type ${serviceName} = _SERVICE
104
93
 
105
94
  /**
106
- * ${pascalName} Display Reactor
95
+ * ${pascalName} Reactor
96
+ *
97
+ * Auto-generated by @ic-reactor/codegen \u2014 do not edit.
98
+ * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
107
99
  */
108
- export const ${reactorName} = new ${reactorType}<${serviceName}>({
100
+ export const ${reactorName} = new DisplayReactor<${serviceName}>({
109
101
  clientManager,
110
102
  idlFactory,
111
103
  name: "${canisterName}",
@@ -122,18 +114,123 @@ export const {
122
114
  `;
123
115
  }
124
116
 
125
- // src/templates/client.ts
117
+ // src/pipeline.ts
118
+ async function runCanisterPipeline(options) {
119
+ const { canisterConfig, projectRoot, globalConfig } = options;
120
+ const { name, didFile, clientManagerPath } = canisterConfig;
121
+ const files = [];
122
+ const resolvedDidFile = path3.isAbsolute(didFile) ? didFile : path3.resolve(projectRoot, didFile);
123
+ if (!fs2.existsSync(resolvedDidFile)) {
124
+ return {
125
+ canisterName: name,
126
+ success: false,
127
+ files: [],
128
+ error: `DID file not found: ${resolvedDidFile}`
129
+ };
130
+ }
131
+ const canisterOutDir = canisterConfig.outDir != null ? path3.isAbsolute(canisterConfig.outDir) ? canisterConfig.outDir : path3.resolve(projectRoot, canisterConfig.outDir) : path3.resolve(projectRoot, globalConfig.outDir, name);
132
+ const resolvedClientManagerPath = clientManagerPath ?? globalConfig.clientManagerPath ?? "../../clients";
133
+ try {
134
+ const declResult = await generateDeclarations({
135
+ didFile: resolvedDidFile,
136
+ outDir: canisterOutDir,
137
+ canisterName: name
138
+ });
139
+ if (!declResult.success) {
140
+ return {
141
+ canisterName: name,
142
+ success: false,
143
+ files,
144
+ error: declResult.error
145
+ };
146
+ }
147
+ files.push(...declResult.files);
148
+ } catch (err) {
149
+ return {
150
+ canisterName: name,
151
+ success: false,
152
+ files,
153
+ error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
154
+ };
155
+ }
156
+ const reactorPath = path3.join(canisterOutDir, "index.ts");
157
+ try {
158
+ const reactorContent = generateReactorFile({
159
+ canisterName: name,
160
+ didFile: resolvedDidFile,
161
+ clientManagerPath: resolvedClientManagerPath
162
+ });
163
+ fs2.mkdirSync(canisterOutDir, { recursive: true });
164
+ fs2.writeFileSync(reactorPath, reactorContent);
165
+ files.push({ success: true, filePath: reactorPath });
166
+ } catch (err) {
167
+ files.push({
168
+ success: false,
169
+ filePath: reactorPath,
170
+ error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`
171
+ });
172
+ return {
173
+ canisterName: name,
174
+ success: false,
175
+ files,
176
+ error: `Reactor step failed: ${err instanceof Error ? err.message : String(err)}`
177
+ };
178
+ }
179
+ return {
180
+ canisterName: name,
181
+ success: true,
182
+ files
183
+ };
184
+ }
185
+
186
+ // src/parser.ts
187
+ import { didToJs as didToJs2 } from "@ic-reactor/parser";
188
+ import fs3 from "fs";
189
+ function extractMethods(didContent) {
190
+ try {
191
+ const jsContent = didToJs2(didContent);
192
+ const methods = [];
193
+ const serviceMatch = /IDL\.Service\(\{([\s\S]*?)\}\)/.exec(jsContent);
194
+ if (!serviceMatch) return methods;
195
+ const serviceBody = serviceMatch[1];
196
+ const methodRegex = /['"]([\w]+)['"]\s*:\s*IDL\.Func\(\s*\[(.*?)\],\s*\[.*?\],\s*\[(.*?)\]\)/g;
197
+ let match;
198
+ while ((match = methodRegex.exec(serviceBody)) !== null) {
199
+ const [, name, args, annotations] = match;
200
+ methods.push({
201
+ name,
202
+ type: annotations.includes("'query'") ? "query" : "mutation",
203
+ hasArgs: args.trim().length > 0
204
+ });
205
+ }
206
+ return methods;
207
+ } catch (error) {
208
+ const msg = error instanceof Error ? error.message : String(error);
209
+ throw new Error(`Failed to parse Candid: ${msg}`);
210
+ }
211
+ }
212
+ function parseDIDFile(didFilePath) {
213
+ if (!fs3.existsSync(didFilePath)) {
214
+ throw new Error(`DID file not found: ${didFilePath}`);
215
+ }
216
+ const content = fs3.readFileSync(didFilePath, "utf-8");
217
+ return extractMethods(content);
218
+ }
219
+
220
+ // src/generators/client.ts
126
221
  function generateClientFile(options = {}) {
127
222
  const { queryClientPath } = options;
128
- return `import { ClientManager } from "@ic-reactor/react"
129
- ${queryClientPath ? `import { queryClient } from "${queryClientPath}"` : `import { QueryClient } from "@tanstack/react-query"
223
+ const queryClientImport = queryClientPath ? `import { queryClient } from "${queryClientPath}"` : `import { QueryClient } from "@tanstack/react-query"
130
224
 
131
- export const queryClient = new QueryClient()`}
225
+ export const queryClient = new QueryClient()`;
226
+ return `import { ClientManager } from "@ic-reactor/react"
227
+ ${queryClientImport}
132
228
 
133
229
  /**
134
230
  * IC Reactor Client Manager
135
- *
136
- * Auto-generated by @ic-reactor/codegen
231
+ *
232
+ * Auto-generated by @ic-reactor/codegen \u2014 customize as needed.
233
+ * See: https://github.com/B3Pay/ic-reactor#client-manager
137
234
  */
138
235
  export const clientManager = new ClientManager({
139
236
  queryClient,
@@ -143,10 +240,13 @@ export const clientManager = new ClientManager({
143
240
  }
144
241
  export {
145
242
  declarationsExist,
243
+ extractMethods,
146
244
  generateClientFile,
147
245
  generateDeclarations,
148
246
  generateReactorFile,
149
247
  getReactorName,
150
248
  getServiceTypeName,
249
+ parseDIDFile,
250
+ runCanisterPipeline,
151
251
  toPascalCase
152
252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/codegen",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Shared code generation utilities for IC Reactor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
2
- import { generateDeclarations } from "./bindgen"
3
2
  import fs from "node:fs"
4
3
  import path from "node:path"
4
+ import { generateDeclarations } from "./generators"
5
5
 
6
6
  describe("Bindgen", () => {
7
7
  const mockDidFile = "mock/test.did"
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Client Manager Generator
3
+ *
4
+ * Generates a central `clients.ts` file that creates a `ClientManager` instance.
5
+ * This file is shared across all canisters in a project.
6
+ *
7
+ * Users typically customize this file once to configure:
8
+ * - Custom host (local vs. mainnet)
9
+ * - Identity provider
10
+ * - Query client options
11
+ */
12
+
13
+ export interface ClientGeneratorOptions {
14
+ /**
15
+ * Import path to an existing `queryClient` instance.
16
+ * If omitted, a default `QueryClient` will be created.
17
+ */
18
+ queryClientPath?: string
19
+ }
20
+
21
+ /**
22
+ * Generate the content for a central client manager file (`clients.ts`).
23
+ */
24
+ export function generateClientFile(
25
+ options: ClientGeneratorOptions = {}
26
+ ): string {
27
+ const { queryClientPath } = options
28
+
29
+ const queryClientImport = queryClientPath
30
+ ? `import { queryClient } from "${queryClientPath}"`
31
+ : `import { QueryClient } from "@tanstack/react-query"
32
+
33
+ export const queryClient = new QueryClient()`
34
+
35
+ return `import { ClientManager } from "@ic-reactor/react"
36
+ ${queryClientImport}
37
+
38
+ /**
39
+ * IC Reactor Client Manager
40
+ *
41
+ * Auto-generated by @ic-reactor/codegen — customize as needed.
42
+ * See: https://github.com/B3Pay/ic-reactor#client-manager
43
+ */
44
+ export const clientManager = new ClientManager({
45
+ queryClient,
46
+ withCanisterEnv: true,
47
+ })
48
+ `
49
+ }