@ic-reactor/codegen 0.6.0 → 0.7.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/README.md CHANGED
@@ -17,81 +17,35 @@ import { runCanisterPipeline } from "@ic-reactor/codegen"
17
17
  await runCanisterPipeline({
18
18
  canisterConfig: {
19
19
  name: "backend",
20
+ mode: "DisplayReactor",
20
21
  didFile: "./backend.did",
21
22
  },
22
23
  projectRoot: process.cwd(),
23
24
  globalConfig: {
24
25
  outDir: "src/declarations",
25
26
  clientManagerPath: "../../clients",
26
- reactor: {
27
- defaultMode: "display",
28
- canisters: {
29
- workflow_engine: "raw",
30
- },
31
- },
32
27
  },
33
28
  })
34
29
  ```
35
30
 
36
- ## Reactor Mode Configuration
31
+ ## Reactor Class Configuration
37
32
 
38
- `reactor.defaultMode` controls whether generated default hook exports use:
33
+ Set `canisterConfig.mode` to choose the generated reactor class:
39
34
 
40
- - `display` → `DisplayReactor` (current/default behavior)
41
- - `raw` → `Reactor` (raw Candid shapes)
35
+ - `DisplayReactor` (default)
36
+ - `Reactor`
37
+ - `CandidReactor`
38
+ - `CandidDisplayReactor`
39
+ - `MetadataDisplayReactor`
42
40
 
43
- `reactor.canisters` lets you override specific canisters without editing generated files.
44
-
45
- ## Generated File Layout (Stable Wrapper)
46
-
47
- Each canister now generates:
48
-
49
- - `index.generated.ts` (regenerated on every run)
50
- - `index.ts` (created only if missing; not overwritten)
51
-
52
- The stable wrapper defaults to:
53
-
54
- ```ts
55
- export * from "./index.generated"
56
- ```
57
-
58
- This lets applications customize exports once and keep them across regenerations.
59
-
60
- ## Generated Output Examples
61
-
62
- Display default:
63
-
64
- ```ts
65
- export function createBackendRawReactor() {
66
- /* ... */
67
- }
68
- export function createBackendDisplayReactor() {
69
- /* ... */
70
- }
71
- export const backendReactor = createBackendDisplayReactor()
72
- export const BackendReactorMode = "display" as const
73
- ```
74
-
75
- Raw default:
76
-
77
- ```ts
78
- export function createWorkflowEngineRawReactor() {
79
- /* ... */
80
- }
81
- export function createWorkflowEngineDisplayReactor() {
82
- /* ... */
83
- }
84
- export const workflowEngineReactor = createWorkflowEngineRawReactor()
85
- export const WorkflowEngineReactorMode = "raw" as const
86
- ```
41
+ Codegen writes a single `index.ts`. It overwrites that file only while it is still recognized as a generated file; if you replace it with your own file, regeneration leaves it untouched.
87
42
 
88
43
  ## Generators
89
44
 
90
45
  You can also use individual generators if you need more granular control:
91
46
 
92
47
  - **`generateDeclarations`**: Generates `.js` (factory), `.d.ts` (types), and `.did` copy.
93
- - **`generateReactorFile`**: Generates the `index.generated.ts` implementation with raw/display factories and typed hooks.
94
- - **`generateReactorWrapperFile`**: Generates the stable `index.ts` wrapper (create-once, preserve-on-regenerate).
48
+ - **`generateReactorFile`**: Generates the `index.ts` file using either `DisplayReactor` or `Reactor`.
95
49
  - **`generateClientFile`**: Generates a `ClientManager` boilerplate file.
96
50
 
97
51
  ## Utilities
package/dist/index.cjs CHANGED
@@ -35,7 +35,6 @@ __export(index_exports, {
35
35
  generateClientFile: () => generateClientFile,
36
36
  generateDeclarations: () => generateDeclarations,
37
37
  generateReactorFile: () => generateReactorFile,
38
- generateReactorWrapperFile: () => generateReactorWrapperFile,
39
38
  getReactorName: () => getReactorName,
40
39
  getServiceTypeName: () => getServiceTypeName,
41
40
  parseDIDFile: () => parseDIDFile,
@@ -124,22 +123,32 @@ function getServiceTypeName(canisterName) {
124
123
  }
125
124
 
126
125
  // src/generators/reactor.ts
126
+ function getReactorClassImportSource(reactorClass) {
127
+ switch (reactorClass) {
128
+ case "Reactor":
129
+ case "DisplayReactor":
130
+ return "@ic-reactor/react";
131
+ case "CandidReactor":
132
+ case "CandidDisplayReactor":
133
+ case "MetadataDisplayReactor":
134
+ return "@ic-reactor/candid";
135
+ }
136
+ }
127
137
  function generateReactorFile(options) {
128
138
  const {
129
139
  canisterName,
130
140
  didFile,
131
141
  clientManagerPath = "../../clients",
132
- reactorMode = "display"
142
+ reactorClass = "DisplayReactor"
133
143
  } = options;
134
144
  const pascalName = toPascalCase(canisterName);
135
145
  const reactorName = getReactorName(canisterName);
136
146
  const serviceName = getServiceTypeName(canisterName);
137
- const rawFactoryName = `create${pascalName}RawReactor`;
138
- const displayFactoryName = `create${pascalName}DisplayReactor`;
139
- const defaultFactoryName = reactorMode === "raw" ? rawFactoryName : displayFactoryName;
140
147
  const baseName = import_node_path2.default.basename(didFile, ".did");
141
148
  const declarationsPath = `./declarations/${baseName}`;
142
- return `import { DisplayReactor, Reactor, createActorHooks } from "@ic-reactor/react"
149
+ const reactorImportSource = getReactorClassImportSource(reactorClass);
150
+ return `import { createActorHooks } from "@ic-reactor/react"
151
+ import { ${reactorClass} } from "${reactorImportSource}"
143
152
  import { clientManager } from "${clientManagerPath}"
144
153
  import { idlFactory, type _SERVICE } from "${declarationsPath}"
145
154
 
@@ -151,25 +160,11 @@ export type ${serviceName} = _SERVICE
151
160
  * Auto-generated by @ic-reactor/codegen \u2014 do not edit.
152
161
  * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
153
162
  */
154
- export function ${rawFactoryName}() {
155
- return new Reactor<${serviceName}>({
156
- clientManager,
157
- idlFactory,
158
- name: "${canisterName}",
159
- })
160
- }
161
-
162
- export function ${displayFactoryName}() {
163
- return new DisplayReactor<${serviceName}>({
164
- clientManager,
165
- idlFactory,
166
- name: "${canisterName}",
167
- })
168
- }
169
-
170
- export const ${reactorName} = ${defaultFactoryName}()
171
-
172
- export const ${pascalName}ReactorMode = "${reactorMode}" as const
163
+ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
164
+ clientManager,
165
+ idlFactory,
166
+ name: "${canisterName}",
167
+ })
173
168
 
174
169
  export const {
175
170
  useActorQuery: use${pascalName}Query,
@@ -181,28 +176,13 @@ export const {
181
176
  } = createActorHooks(${reactorName})
182
177
  `;
183
178
  }
184
- function generateReactorWrapperFile(options) {
185
- const { canisterName } = options;
186
- const pascalName = toPascalCase(canisterName);
187
- return `/**
188
- * ${pascalName} canister exports
189
- *
190
- * This wrapper is created once by @ic-reactor/codegen and is not overwritten.
191
- * Customize it if you need to swap reactor implementations or compose custom exports.
192
- */
193
- export * from "./index.generated"
194
- `;
195
- }
196
179
 
197
180
  // src/pipeline.ts
198
- function resolveReactorMode(canisterName, globalConfig) {
199
- return globalConfig.reactor?.canisters?.[canisterName] ?? globalConfig.reactor?.defaultMode ?? "display";
200
- }
201
- function isStableWrapperFile(content) {
202
- return content.includes('export * from "./index.generated"');
181
+ function resolveReactorClass(canisterConfig) {
182
+ return canisterConfig.mode ?? "DisplayReactor";
203
183
  }
204
- function isLegacyGeneratedIndexFile(content) {
205
- return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(") && !isStableWrapperFile(content);
184
+ function isGeneratedIndexFile(content) {
185
+ return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(");
206
186
  }
207
187
  async function runCanisterPipeline(options) {
208
188
  const { canisterConfig, projectRoot, globalConfig } = options;
@@ -242,41 +222,32 @@ async function runCanisterPipeline(options) {
242
222
  error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
243
223
  };
244
224
  }
245
- const generatedReactorPath = import_node_path3.default.join(canisterOutDir, "index.generated.ts");
246
- const wrapperPath = import_node_path3.default.join(canisterOutDir, "index.ts");
247
- const reactorMode = resolveReactorMode(name, globalConfig);
225
+ const reactorPath = import_node_path3.default.join(canisterOutDir, "index.ts");
226
+ const reactorClass = resolveReactorClass(canisterConfig);
248
227
  try {
249
228
  const reactorContent = generateReactorFile({
250
229
  canisterName: name,
251
230
  didFile: resolvedDidFile,
252
231
  clientManagerPath: resolvedClientManagerPath,
253
- reactorMode
254
- });
255
- const wrapperContent = generateReactorWrapperFile({
256
- canisterName: name,
257
- didFile: resolvedDidFile,
258
- clientManagerPath: resolvedClientManagerPath,
259
- reactorMode
232
+ reactorClass
260
233
  });
261
234
  import_node_fs2.default.mkdirSync(canisterOutDir, { recursive: true });
262
- import_node_fs2.default.writeFileSync(generatedReactorPath, reactorContent);
263
- files.push({ success: true, filePath: generatedReactorPath });
264
- if (!import_node_fs2.default.existsSync(wrapperPath)) {
265
- import_node_fs2.default.writeFileSync(wrapperPath, wrapperContent);
266
- files.push({ success: true, filePath: wrapperPath });
235
+ if (!import_node_fs2.default.existsSync(reactorPath)) {
236
+ import_node_fs2.default.writeFileSync(reactorPath, reactorContent);
237
+ files.push({ success: true, filePath: reactorPath });
267
238
  } else {
268
- const existingWrapperContent = import_node_fs2.default.readFileSync(wrapperPath, "utf-8");
269
- if (isLegacyGeneratedIndexFile(existingWrapperContent)) {
270
- import_node_fs2.default.writeFileSync(wrapperPath, wrapperContent);
271
- files.push({ success: true, filePath: wrapperPath });
239
+ const existingIndexContent = import_node_fs2.default.readFileSync(reactorPath, "utf-8");
240
+ if (isGeneratedIndexFile(existingIndexContent)) {
241
+ import_node_fs2.default.writeFileSync(reactorPath, reactorContent);
242
+ files.push({ success: true, filePath: reactorPath });
272
243
  } else {
273
- files.push({ success: true, filePath: wrapperPath, skipped: true });
244
+ files.push({ success: true, filePath: reactorPath, skipped: true });
274
245
  }
275
246
  }
276
247
  } catch (err) {
277
248
  files.push({
278
249
  success: false,
279
- filePath: generatedReactorPath,
250
+ filePath: reactorPath,
280
251
  error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`
281
252
  });
282
253
  return {
@@ -355,7 +326,6 @@ export const clientManager = new ClientManager({
355
326
  generateClientFile,
356
327
  generateDeclarations,
357
328
  generateReactorFile,
358
- generateReactorWrapperFile,
359
329
  getReactorName,
360
330
  getServiceTypeName,
361
331
  parseDIDFile,
package/dist/index.d.cts CHANGED
@@ -19,22 +19,15 @@ interface CanisterConfig {
19
19
  * Example: "../../clients" → `import { clientManager } from "../../clients"`
20
20
  */
21
21
  clientManagerPath?: string;
22
- /** Optional fixed canister ID */
23
- canisterId?: string;
24
- }
25
- type ReactorMode = "raw" | "display";
26
- interface ReactorGenerationConfig {
27
- /**
28
- * Default reactor mode used for generated hook exports.
29
- * `display` preserves current behavior.
30
- */
31
- defaultMode?: ReactorMode;
32
22
  /**
33
- * Optional per-canister overrides keyed by canister name.
34
- * Example: { workflow_engine: "raw" }
23
+ * Reactor class used for generated hooks in this canister.
24
+ * Defaults to DisplayReactor for backward compatibility.
35
25
  */
36
- canisters?: Record<string, ReactorMode>;
26
+ mode?: ReactorClassName;
27
+ /** Optional fixed canister ID */
28
+ canisterId?: string;
37
29
  }
30
+ type ReactorClassName = "Reactor" | "DisplayReactor" | "CandidReactor" | "CandidDisplayReactor" | "MetadataDisplayReactor";
38
31
  /**
39
32
  * Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
40
33
  */
@@ -51,8 +44,6 @@ interface CodegenConfig {
51
44
  * Individual canisters can override via `CanisterConfig.clientManagerPath`.
52
45
  */
53
46
  clientManagerPath?: string;
54
- /** Optional reactor mode generation settings */
55
- reactor?: ReactorGenerationConfig;
56
47
  /** Canister configurations, keyed by canister name */
57
48
  canisters: Record<string, CanisterConfig>;
58
49
  }
@@ -77,7 +68,7 @@ interface GeneratorResult {
77
68
  * Pipeline steps (in order):
78
69
  * 1. Resolve paths (didFile, outDir)
79
70
  * 2. Generate declarations (JS + .d.ts + .did copy)
80
- * 3. Generate reactor implementation + stable wrapper (`index.generated.ts` + `index.ts`)
71
+ * 3. Generate reactor file (`index.ts`)
81
72
  */
82
73
 
83
74
  interface PipelineOptions {
@@ -91,7 +82,7 @@ interface PipelineOptions {
91
82
  /**
92
83
  * Global codegen config (for fallback outDir and clientManagerPath).
93
84
  */
94
- globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "reactor">;
85
+ globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
95
86
  }
96
87
  interface PipelineResult {
97
88
  canisterName: string;
@@ -234,20 +225,15 @@ interface ReactorGeneratorOptions {
234
225
  */
235
226
  clientManagerPath?: string;
236
227
  /**
237
- * Which reactor implementation should back the default exported hooks.
238
- * Default: "display" (backward compatible)
228
+ * Which reactor class should back the generated hooks.
229
+ * Default: "DisplayReactor" (backward compatible)
239
230
  */
240
- reactorMode?: ReactorMode;
231
+ reactorClass?: ReactorClassName;
241
232
  }
242
233
  /**
243
- * Generate the content of a canister's `index.generated.ts` implementation file.
234
+ * Generate the content of a canister's `index.ts` file.
244
235
  */
245
236
  declare function generateReactorFile(options: ReactorGeneratorOptions): string;
246
- /**
247
- * Generate the stable user wrapper `index.ts` for a canister.
248
- * This file is intended to be created once and preserved across regenerations.
249
- */
250
- declare function generateReactorWrapperFile(options: ReactorGeneratorOptions): string;
251
237
 
252
238
  /**
253
239
  * Client Manager Generator
@@ -272,4 +258,4 @@ interface ClientGeneratorOptions {
272
258
  */
273
259
  declare function generateClientFile(options?: ClientGeneratorOptions): string;
274
260
 
275
- export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorGenerationConfig, type ReactorGeneratorOptions, type ReactorMode, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, generateReactorWrapperFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
261
+ export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
package/dist/index.d.ts CHANGED
@@ -19,22 +19,15 @@ interface CanisterConfig {
19
19
  * Example: "../../clients" → `import { clientManager } from "../../clients"`
20
20
  */
21
21
  clientManagerPath?: string;
22
- /** Optional fixed canister ID */
23
- canisterId?: string;
24
- }
25
- type ReactorMode = "raw" | "display";
26
- interface ReactorGenerationConfig {
27
- /**
28
- * Default reactor mode used for generated hook exports.
29
- * `display` preserves current behavior.
30
- */
31
- defaultMode?: ReactorMode;
32
22
  /**
33
- * Optional per-canister overrides keyed by canister name.
34
- * Example: { workflow_engine: "raw" }
23
+ * Reactor class used for generated hooks in this canister.
24
+ * Defaults to DisplayReactor for backward compatibility.
35
25
  */
36
- canisters?: Record<string, ReactorMode>;
26
+ mode?: ReactorClassName;
27
+ /** Optional fixed canister ID */
28
+ canisterId?: string;
37
29
  }
30
+ type ReactorClassName = "Reactor" | "DisplayReactor" | "CandidReactor" | "CandidDisplayReactor" | "MetadataDisplayReactor";
38
31
  /**
39
32
  * Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
40
33
  */
@@ -51,8 +44,6 @@ interface CodegenConfig {
51
44
  * Individual canisters can override via `CanisterConfig.clientManagerPath`.
52
45
  */
53
46
  clientManagerPath?: string;
54
- /** Optional reactor mode generation settings */
55
- reactor?: ReactorGenerationConfig;
56
47
  /** Canister configurations, keyed by canister name */
57
48
  canisters: Record<string, CanisterConfig>;
58
49
  }
@@ -77,7 +68,7 @@ interface GeneratorResult {
77
68
  * Pipeline steps (in order):
78
69
  * 1. Resolve paths (didFile, outDir)
79
70
  * 2. Generate declarations (JS + .d.ts + .did copy)
80
- * 3. Generate reactor implementation + stable wrapper (`index.generated.ts` + `index.ts`)
71
+ * 3. Generate reactor file (`index.ts`)
81
72
  */
82
73
 
83
74
  interface PipelineOptions {
@@ -91,7 +82,7 @@ interface PipelineOptions {
91
82
  /**
92
83
  * Global codegen config (for fallback outDir and clientManagerPath).
93
84
  */
94
- globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "reactor">;
85
+ globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
95
86
  }
96
87
  interface PipelineResult {
97
88
  canisterName: string;
@@ -234,20 +225,15 @@ interface ReactorGeneratorOptions {
234
225
  */
235
226
  clientManagerPath?: string;
236
227
  /**
237
- * Which reactor implementation should back the default exported hooks.
238
- * Default: "display" (backward compatible)
228
+ * Which reactor class should back the generated hooks.
229
+ * Default: "DisplayReactor" (backward compatible)
239
230
  */
240
- reactorMode?: ReactorMode;
231
+ reactorClass?: ReactorClassName;
241
232
  }
242
233
  /**
243
- * Generate the content of a canister's `index.generated.ts` implementation file.
234
+ * Generate the content of a canister's `index.ts` file.
244
235
  */
245
236
  declare function generateReactorFile(options: ReactorGeneratorOptions): string;
246
- /**
247
- * Generate the stable user wrapper `index.ts` for a canister.
248
- * This file is intended to be created once and preserved across regenerations.
249
- */
250
- declare function generateReactorWrapperFile(options: ReactorGeneratorOptions): string;
251
237
 
252
238
  /**
253
239
  * Client Manager Generator
@@ -272,4 +258,4 @@ interface ClientGeneratorOptions {
272
258
  */
273
259
  declare function generateClientFile(options?: ClientGeneratorOptions): string;
274
260
 
275
- export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorGenerationConfig, type ReactorGeneratorOptions, type ReactorMode, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, generateReactorWrapperFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
261
+ export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
package/dist/index.js CHANGED
@@ -78,22 +78,32 @@ function getServiceTypeName(canisterName) {
78
78
  }
79
79
 
80
80
  // src/generators/reactor.ts
81
+ function getReactorClassImportSource(reactorClass) {
82
+ switch (reactorClass) {
83
+ case "Reactor":
84
+ case "DisplayReactor":
85
+ return "@ic-reactor/react";
86
+ case "CandidReactor":
87
+ case "CandidDisplayReactor":
88
+ case "MetadataDisplayReactor":
89
+ return "@ic-reactor/candid";
90
+ }
91
+ }
81
92
  function generateReactorFile(options) {
82
93
  const {
83
94
  canisterName,
84
95
  didFile,
85
96
  clientManagerPath = "../../clients",
86
- reactorMode = "display"
97
+ reactorClass = "DisplayReactor"
87
98
  } = options;
88
99
  const pascalName = toPascalCase(canisterName);
89
100
  const reactorName = getReactorName(canisterName);
90
101
  const serviceName = getServiceTypeName(canisterName);
91
- const rawFactoryName = `create${pascalName}RawReactor`;
92
- const displayFactoryName = `create${pascalName}DisplayReactor`;
93
- const defaultFactoryName = reactorMode === "raw" ? rawFactoryName : displayFactoryName;
94
102
  const baseName = path2.basename(didFile, ".did");
95
103
  const declarationsPath = `./declarations/${baseName}`;
96
- return `import { DisplayReactor, Reactor, createActorHooks } from "@ic-reactor/react"
104
+ const reactorImportSource = getReactorClassImportSource(reactorClass);
105
+ return `import { createActorHooks } from "@ic-reactor/react"
106
+ import { ${reactorClass} } from "${reactorImportSource}"
97
107
  import { clientManager } from "${clientManagerPath}"
98
108
  import { idlFactory, type _SERVICE } from "${declarationsPath}"
99
109
 
@@ -105,25 +115,11 @@ export type ${serviceName} = _SERVICE
105
115
  * Auto-generated by @ic-reactor/codegen \u2014 do not edit.
106
116
  * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
107
117
  */
108
- export function ${rawFactoryName}() {
109
- return new Reactor<${serviceName}>({
110
- clientManager,
111
- idlFactory,
112
- name: "${canisterName}",
113
- })
114
- }
115
-
116
- export function ${displayFactoryName}() {
117
- return new DisplayReactor<${serviceName}>({
118
- clientManager,
119
- idlFactory,
120
- name: "${canisterName}",
121
- })
122
- }
123
-
124
- export const ${reactorName} = ${defaultFactoryName}()
125
-
126
- export const ${pascalName}ReactorMode = "${reactorMode}" as const
118
+ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
119
+ clientManager,
120
+ idlFactory,
121
+ name: "${canisterName}",
122
+ })
127
123
 
128
124
  export const {
129
125
  useActorQuery: use${pascalName}Query,
@@ -135,28 +131,13 @@ export const {
135
131
  } = createActorHooks(${reactorName})
136
132
  `;
137
133
  }
138
- function generateReactorWrapperFile(options) {
139
- const { canisterName } = options;
140
- const pascalName = toPascalCase(canisterName);
141
- return `/**
142
- * ${pascalName} canister exports
143
- *
144
- * This wrapper is created once by @ic-reactor/codegen and is not overwritten.
145
- * Customize it if you need to swap reactor implementations or compose custom exports.
146
- */
147
- export * from "./index.generated"
148
- `;
149
- }
150
134
 
151
135
  // src/pipeline.ts
152
- function resolveReactorMode(canisterName, globalConfig) {
153
- return globalConfig.reactor?.canisters?.[canisterName] ?? globalConfig.reactor?.defaultMode ?? "display";
154
- }
155
- function isStableWrapperFile(content) {
156
- return content.includes('export * from "./index.generated"');
136
+ function resolveReactorClass(canisterConfig) {
137
+ return canisterConfig.mode ?? "DisplayReactor";
157
138
  }
158
- function isLegacyGeneratedIndexFile(content) {
159
- return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(") && !isStableWrapperFile(content);
139
+ function isGeneratedIndexFile(content) {
140
+ return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(");
160
141
  }
161
142
  async function runCanisterPipeline(options) {
162
143
  const { canisterConfig, projectRoot, globalConfig } = options;
@@ -196,41 +177,32 @@ async function runCanisterPipeline(options) {
196
177
  error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
197
178
  };
198
179
  }
199
- const generatedReactorPath = path3.join(canisterOutDir, "index.generated.ts");
200
- const wrapperPath = path3.join(canisterOutDir, "index.ts");
201
- const reactorMode = resolveReactorMode(name, globalConfig);
180
+ const reactorPath = path3.join(canisterOutDir, "index.ts");
181
+ const reactorClass = resolveReactorClass(canisterConfig);
202
182
  try {
203
183
  const reactorContent = generateReactorFile({
204
184
  canisterName: name,
205
185
  didFile: resolvedDidFile,
206
186
  clientManagerPath: resolvedClientManagerPath,
207
- reactorMode
208
- });
209
- const wrapperContent = generateReactorWrapperFile({
210
- canisterName: name,
211
- didFile: resolvedDidFile,
212
- clientManagerPath: resolvedClientManagerPath,
213
- reactorMode
187
+ reactorClass
214
188
  });
215
189
  fs2.mkdirSync(canisterOutDir, { recursive: true });
216
- fs2.writeFileSync(generatedReactorPath, reactorContent);
217
- files.push({ success: true, filePath: generatedReactorPath });
218
- if (!fs2.existsSync(wrapperPath)) {
219
- fs2.writeFileSync(wrapperPath, wrapperContent);
220
- files.push({ success: true, filePath: wrapperPath });
190
+ if (!fs2.existsSync(reactorPath)) {
191
+ fs2.writeFileSync(reactorPath, reactorContent);
192
+ files.push({ success: true, filePath: reactorPath });
221
193
  } else {
222
- const existingWrapperContent = fs2.readFileSync(wrapperPath, "utf-8");
223
- if (isLegacyGeneratedIndexFile(existingWrapperContent)) {
224
- fs2.writeFileSync(wrapperPath, wrapperContent);
225
- files.push({ success: true, filePath: wrapperPath });
194
+ const existingIndexContent = fs2.readFileSync(reactorPath, "utf-8");
195
+ if (isGeneratedIndexFile(existingIndexContent)) {
196
+ fs2.writeFileSync(reactorPath, reactorContent);
197
+ files.push({ success: true, filePath: reactorPath });
226
198
  } else {
227
- files.push({ success: true, filePath: wrapperPath, skipped: true });
199
+ files.push({ success: true, filePath: reactorPath, skipped: true });
228
200
  }
229
201
  }
230
202
  } catch (err) {
231
203
  files.push({
232
204
  success: false,
233
- filePath: generatedReactorPath,
205
+ filePath: reactorPath,
234
206
  error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`
235
207
  });
236
208
  return {
@@ -308,7 +280,6 @@ export {
308
280
  generateClientFile,
309
281
  generateDeclarations,
310
282
  generateReactorFile,
311
- generateReactorWrapperFile,
312
283
  getReactorName,
313
284
  getServiceTypeName,
314
285
  parseDIDFile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/codegen",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Shared code generation utilities for IC Reactor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,18 +1,8 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`Reactor generator > generates a stable wrapper file > stable-wrapper-index 1`] = `
4
- "/**
5
- * Backend canister exports
6
- *
7
- * This wrapper is created once by @ic-reactor/codegen and is not overwritten.
8
- * Customize it if you need to swap reactor implementations or compose custom exports.
9
- */
10
- export * from "./index.generated"
11
- "
12
- `;
13
-
14
- exports[`Reactor generator > keeps default behavior as display mode > display-mode-index-generated 1`] = `
15
- "import { DisplayReactor, Reactor, createActorHooks } from "@ic-reactor/react"
3
+ exports[`Reactor generator > keeps default behavior as DisplayReactor > display-reactor-index 1`] = `
4
+ "import { createActorHooks } from "@ic-reactor/react"
5
+ import { DisplayReactor } from "@ic-reactor/react"
16
6
  import { clientManager } from "../../clients"
17
7
  import { idlFactory, type _SERVICE } from "./declarations/backend"
18
8
 
@@ -24,25 +14,11 @@ export type BackendService = _SERVICE
24
14
  * Auto-generated by @ic-reactor/codegen — do not edit.
25
15
  * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
26
16
  */
27
- export function createBackendRawReactor() {
28
- return new Reactor<BackendService>({
29
- clientManager,
30
- idlFactory,
31
- name: "backend",
32
- })
33
- }
34
-
35
- export function createBackendDisplayReactor() {
36
- return new DisplayReactor<BackendService>({
37
- clientManager,
38
- idlFactory,
39
- name: "backend",
40
- })
41
- }
42
-
43
- export const backendReactor = createBackendDisplayReactor()
44
-
45
- export const BackendReactorMode = "display" as const
17
+ export const backendReactor = new DisplayReactor<BackendService>({
18
+ clientManager,
19
+ idlFactory,
20
+ name: "backend",
21
+ })
46
22
 
47
23
  export const {
48
24
  useActorQuery: useBackendQuery,
@@ -55,8 +31,9 @@ export const {
55
31
  "
56
32
  `;
57
33
 
58
- exports[`Reactor generator > supports raw mode generation > raw-mode-index-generated 1`] = `
59
- "import { DisplayReactor, Reactor, createActorHooks } from "@ic-reactor/react"
34
+ exports[`Reactor generator > supports Reactor mode generation > reactor-index 1`] = `
35
+ "import { createActorHooks } from "@ic-reactor/react"
36
+ import { Reactor } from "@ic-reactor/react"
60
37
  import { clientManager } from "../../clients"
61
38
  import { idlFactory, type _SERVICE } from "./declarations/workflow_engine"
62
39
 
@@ -68,25 +45,11 @@ export type WorkflowEngineService = _SERVICE
68
45
  * Auto-generated by @ic-reactor/codegen — do not edit.
69
46
  * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
70
47
  */
71
- export function createWorkflowEngineRawReactor() {
72
- return new Reactor<WorkflowEngineService>({
73
- clientManager,
74
- idlFactory,
75
- name: "workflow_engine",
76
- })
77
- }
78
-
79
- export function createWorkflowEngineDisplayReactor() {
80
- return new DisplayReactor<WorkflowEngineService>({
81
- clientManager,
82
- idlFactory,
83
- name: "workflow_engine",
84
- })
85
- }
86
-
87
- export const workflowEngineReactor = createWorkflowEngineRawReactor()
88
-
89
- export const WorkflowEngineReactorMode = "raw" as const
48
+ export const workflowEngineReactor = new Reactor<WorkflowEngineService>({
49
+ clientManager,
50
+ idlFactory,
51
+ name: "workflow_engine",
52
+ })
90
53
 
91
54
  export const {
92
55
  useActorQuery: useWorkflowEngineQuery,
@@ -98,3 +61,34 @@ export const {
98
61
  } = createActorHooks(workflowEngineReactor)
99
62
  "
100
63
  `;
64
+
65
+ exports[`Reactor generator > supports candid reactor subclasses > metadata-display-reactor-index 1`] = `
66
+ "import { createActorHooks } from "@ic-reactor/react"
67
+ import { MetadataDisplayReactor } from "@ic-reactor/candid"
68
+ import { clientManager } from "../../clients"
69
+ import { idlFactory, type _SERVICE } from "./declarations/ledger"
70
+
71
+ export type LedgerService = _SERVICE
72
+
73
+ /**
74
+ * Ledger Reactor
75
+ *
76
+ * Auto-generated by @ic-reactor/codegen — do not edit.
77
+ * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
78
+ */
79
+ export const ledgerReactor = new MetadataDisplayReactor<LedgerService>({
80
+ clientManager,
81
+ idlFactory,
82
+ name: "ledger",
83
+ })
84
+
85
+ export const {
86
+ useActorQuery: useLedgerQuery,
87
+ useActorSuspenseQuery: useLedgerSuspenseQuery,
88
+ useActorInfiniteQuery: useLedgerInfiniteQuery,
89
+ useActorSuspenseInfiniteQuery: useLedgerSuspenseInfiniteQuery,
90
+ useActorMutation: useLedgerMutation,
91
+ useActorMethod: useLedgerMethod,
92
+ } = createActorHooks(ledgerReactor)
93
+ "
94
+ `;
@@ -11,7 +11,7 @@ export type {
11
11
  DeclarationsGeneratorResult,
12
12
  } from "./declarations.js"
13
13
 
14
- export { generateReactorFile, generateReactorWrapperFile } from "./reactor.js"
14
+ export { generateReactorFile } from "./reactor.js"
15
15
  export type { ReactorGeneratorOptions } from "./reactor.js"
16
16
 
17
17
  export { generateClientFile } from "./client.js"
@@ -22,7 +22,7 @@
22
22
 
23
23
  import path from "node:path"
24
24
  import { toPascalCase, getReactorName, getServiceTypeName } from "../naming.js"
25
- import type { ReactorMode } from "../types.js"
25
+ import type { ReactorClassName } from "../types.js"
26
26
 
27
27
  export interface ReactorGeneratorOptions {
28
28
  /** Canister name (e.g. "backend") */
@@ -38,36 +38,48 @@ export interface ReactorGeneratorOptions {
38
38
  */
39
39
  clientManagerPath?: string
40
40
  /**
41
- * Which reactor implementation should back the default exported hooks.
42
- * Default: "display" (backward compatible)
41
+ * Which reactor class should back the generated hooks.
42
+ * Default: "DisplayReactor" (backward compatible)
43
43
  */
44
- reactorMode?: ReactorMode
44
+ reactorClass?: ReactorClassName
45
+ }
46
+
47
+ function getReactorClassImportSource(
48
+ reactorClass: ReactorClassName
49
+ ): "@ic-reactor/react" | "@ic-reactor/candid" {
50
+ switch (reactorClass) {
51
+ case "Reactor":
52
+ case "DisplayReactor":
53
+ return "@ic-reactor/react"
54
+ case "CandidReactor":
55
+ case "CandidDisplayReactor":
56
+ case "MetadataDisplayReactor":
57
+ return "@ic-reactor/candid"
58
+ }
45
59
  }
46
60
 
47
61
  /**
48
- * Generate the content of a canister's `index.generated.ts` implementation file.
62
+ * Generate the content of a canister's `index.ts` file.
49
63
  */
50
64
  export function generateReactorFile(options: ReactorGeneratorOptions): string {
51
65
  const {
52
66
  canisterName,
53
67
  didFile,
54
68
  clientManagerPath = "../../clients",
55
- reactorMode = "display",
69
+ reactorClass = "DisplayReactor",
56
70
  } = options
57
71
 
58
72
  const pascalName = toPascalCase(canisterName)
59
73
  const reactorName = getReactorName(canisterName)
60
74
  const serviceName = getServiceTypeName(canisterName)
61
- const rawFactoryName = `create${pascalName}RawReactor`
62
- const displayFactoryName = `create${pascalName}DisplayReactor`
63
- const defaultFactoryName =
64
- reactorMode === "raw" ? rawFactoryName : displayFactoryName
65
75
 
66
76
  // Derive the declarations import path from the .did filename
67
77
  const baseName = path.basename(didFile, ".did")
68
78
  const declarationsPath = `./declarations/${baseName}`
79
+ const reactorImportSource = getReactorClassImportSource(reactorClass)
69
80
 
70
- return `import { DisplayReactor, Reactor, createActorHooks } from "@ic-reactor/react"
81
+ return `import { createActorHooks } from "@ic-reactor/react"
82
+ import { ${reactorClass} } from "${reactorImportSource}"
71
83
  import { clientManager } from "${clientManagerPath}"
72
84
  import { idlFactory, type _SERVICE } from "${declarationsPath}"
73
85
 
@@ -79,25 +91,11 @@ export type ${serviceName} = _SERVICE
79
91
  * Auto-generated by @ic-reactor/codegen — do not edit.
80
92
  * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
81
93
  */
82
- export function ${rawFactoryName}() {
83
- return new Reactor<${serviceName}>({
84
- clientManager,
85
- idlFactory,
86
- name: "${canisterName}",
87
- })
88
- }
89
-
90
- export function ${displayFactoryName}() {
91
- return new DisplayReactor<${serviceName}>({
92
- clientManager,
93
- idlFactory,
94
- name: "${canisterName}",
95
- })
96
- }
97
-
98
- export const ${reactorName} = ${defaultFactoryName}()
99
-
100
- export const ${pascalName}ReactorMode = "${reactorMode}" as const
94
+ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
95
+ clientManager,
96
+ idlFactory,
97
+ name: "${canisterName}",
98
+ })
101
99
 
102
100
  export const {
103
101
  useActorQuery: use${pascalName}Query,
@@ -109,23 +107,3 @@ export const {
109
107
  } = createActorHooks(${reactorName})
110
108
  `
111
109
  }
112
-
113
- /**
114
- * Generate the stable user wrapper `index.ts` for a canister.
115
- * This file is intended to be created once and preserved across regenerations.
116
- */
117
- export function generateReactorWrapperFile(
118
- options: ReactorGeneratorOptions
119
- ): string {
120
- const { canisterName } = options
121
- const pascalName = toPascalCase(canisterName)
122
-
123
- return `/**
124
- * ${pascalName} canister exports
125
- *
126
- * This wrapper is created once by @ic-reactor/codegen and is not overwritten.
127
- * Customize it if you need to swap reactor implementations or compose custom exports.
128
- */
129
- export * from "./index.generated"
130
- `
131
- }
package/src/index.ts CHANGED
@@ -10,8 +10,7 @@ export type {
10
10
  CanisterConfig,
11
11
  CodegenConfig,
12
12
  GeneratorResult,
13
- ReactorMode,
14
- ReactorGenerationConfig,
13
+ ReactorClassName,
15
14
  } from "./types.js"
16
15
 
17
16
  // Pipeline (Primary Entry Point)
@@ -31,7 +31,7 @@ describe("Codegen pipeline", () => {
31
31
  )
32
32
  }
33
33
 
34
- it("applies per-canister reactor mode overrides over the global default", async () => {
34
+ it("uses per-canister mode to generate Reactor-based hooks", async () => {
35
35
  const projectRoot = createTempProject()
36
36
  writeDid(projectRoot, "workflow_engine.did")
37
37
 
@@ -39,35 +39,28 @@ describe("Codegen pipeline", () => {
39
39
  canisterConfig: {
40
40
  name: "workflow_engine",
41
41
  didFile: "workflow_engine.did",
42
+ mode: "Reactor",
42
43
  },
43
44
  projectRoot,
44
45
  globalConfig: {
45
46
  outDir: "src/declarations",
46
47
  clientManagerPath: "../../clients",
47
- reactor: {
48
- defaultMode: "display",
49
- canisters: {
50
- workflow_engine: "raw",
51
- },
52
- },
53
48
  },
54
49
  })
55
50
 
56
51
  expect(result.success).toBe(true)
57
52
 
58
- const generatedPath = path.join(
53
+ const indexPath = path.join(
59
54
  projectRoot,
60
- "src/declarations/workflow_engine/index.generated.ts"
55
+ "src/declarations/workflow_engine/index.ts"
61
56
  )
62
- const generated = fs.readFileSync(generatedPath, "utf-8")
57
+ const generated = fs.readFileSync(indexPath, "utf-8")
63
58
 
64
59
  expect(generated).toContain("new Reactor<WorkflowEngineService>")
65
- expect(generated).toContain(
66
- 'export const WorkflowEngineReactorMode = "raw" as const'
67
- )
60
+ expect(generated).not.toContain("new DisplayReactor<WorkflowEngineService>")
68
61
  })
69
62
 
70
- it("does not overwrite the wrapper file on regenerate", async () => {
63
+ it("does not overwrite user-modified index.ts on regenerate", async () => {
71
64
  const projectRoot = createTempProject()
72
65
  writeDid(projectRoot, "backend.did")
73
66
 
@@ -86,26 +79,26 @@ describe("Codegen pipeline", () => {
86
79
  const first = await runCanisterPipeline(options)
87
80
  expect(first.success).toBe(true)
88
81
 
89
- const wrapperPath = path.join(
82
+ const indexPath = path.join(
90
83
  projectRoot,
91
84
  "src/declarations/backend/index.ts"
92
85
  )
93
86
  fs.writeFileSync(
94
- wrapperPath,
95
- `// user wrapper
96
- export const customBackendWrapper = true
87
+ indexPath,
88
+ `// user custom canister file
89
+ export const customBackendIndex = true
97
90
  `
98
91
  )
99
92
 
100
93
  const second = await runCanisterPipeline(options)
101
94
  expect(second.success).toBe(true)
102
95
 
103
- const wrapper = fs.readFileSync(wrapperPath, "utf-8")
104
- expect(wrapper).toContain("customBackendWrapper = true")
96
+ const wrapper = fs.readFileSync(indexPath, "utf-8")
97
+ expect(wrapper).toContain("customBackendIndex = true")
105
98
  expect(second.files).toEqual(
106
99
  expect.arrayContaining([
107
100
  expect.objectContaining({
108
- filePath: wrapperPath,
101
+ filePath: indexPath,
109
102
  skipped: true,
110
103
  success: true,
111
104
  }),
@@ -113,14 +106,14 @@ export const customBackendWrapper = true
113
106
  )
114
107
  })
115
108
 
116
- it("migrates a legacy generated index.ts into a stable wrapper", async () => {
109
+ it("overwrites legacy generated index.ts during regeneration", async () => {
117
110
  const projectRoot = createTempProject()
118
111
  writeDid(projectRoot, "backend.did")
119
112
 
120
113
  const canisterOutDir = path.join(projectRoot, "src/declarations/backend")
121
114
  fs.mkdirSync(canisterOutDir, { recursive: true })
122
115
 
123
- // Simulate the pre-wrapper generated index.ts content from older versions.
116
+ // Simulate a generated index.ts content from older versions.
124
117
  fs.writeFileSync(
125
118
  path.join(canisterOutDir, "index.ts"),
126
119
  `import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
@@ -161,18 +154,10 @@ export const {
161
154
 
162
155
  expect(result.success).toBe(true)
163
156
 
164
- const wrapperPath = path.join(canisterOutDir, "index.ts")
165
- const wrapper = fs.readFileSync(wrapperPath, "utf-8")
166
- expect(wrapper).toContain('export * from "./index.generated"')
167
- expect(wrapper).not.toContain("createActorHooks(")
168
-
169
- const generated = fs.readFileSync(
170
- path.join(canisterOutDir, "index.generated.ts"),
171
- "utf-8"
172
- )
173
- expect(generated).toContain("createBackendDisplayReactor")
174
- expect(generated).toContain(
175
- 'export const BackendReactorMode = "display" as const'
176
- )
157
+ const indexPath = path.join(canisterOutDir, "index.ts")
158
+ const generated = fs.readFileSync(indexPath, "utf-8")
159
+ expect(generated).toContain("new DisplayReactor<BackendService>")
160
+ expect(generated).toContain("useBackendMutation")
161
+ expect(generated).not.toContain('export * from "./index.generated"')
177
162
  })
178
163
  })
package/src/pipeline.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * Pipeline steps (in order):
8
8
  * 1. Resolve paths (didFile, outDir)
9
9
  * 2. Generate declarations (JS + .d.ts + .did copy)
10
- * 3. Generate reactor implementation + stable wrapper (`index.generated.ts` + `index.ts`)
10
+ * 3. Generate reactor file (`index.ts`)
11
11
  */
12
12
 
13
13
  import fs from "node:fs"
@@ -16,13 +16,10 @@ import type {
16
16
  CanisterConfig,
17
17
  CodegenConfig,
18
18
  GeneratorResult,
19
- ReactorMode,
19
+ ReactorClassName,
20
20
  } from "./types.js"
21
21
  import { generateDeclarations } from "./generators/declarations.js"
22
- import {
23
- generateReactorFile,
24
- generateReactorWrapperFile,
25
- } from "./generators/reactor.js"
22
+ import { generateReactorFile } from "./generators/reactor.js"
26
23
 
27
24
  export interface PipelineOptions {
28
25
  /** Canister name and config */
@@ -35,29 +32,17 @@ export interface PipelineOptions {
35
32
  /**
36
33
  * Global codegen config (for fallback outDir and clientManagerPath).
37
34
  */
38
- globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "reactor">
35
+ globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">
39
36
  }
40
37
 
41
- function resolveReactorMode(
42
- canisterName: string,
43
- globalConfig: Pick<CodegenConfig, "reactor">
44
- ): ReactorMode {
45
- return (
46
- globalConfig.reactor?.canisters?.[canisterName] ??
47
- globalConfig.reactor?.defaultMode ??
48
- "display"
49
- )
38
+ function resolveReactorClass(canisterConfig: CanisterConfig): ReactorClassName {
39
+ return canisterConfig.mode ?? "DisplayReactor"
50
40
  }
51
41
 
52
- function isStableWrapperFile(content: string): boolean {
53
- return content.includes('export * from "./index.generated"')
54
- }
55
-
56
- function isLegacyGeneratedIndexFile(content: string): boolean {
42
+ function isGeneratedIndexFile(content: string): boolean {
57
43
  return (
58
44
  content.includes("Auto-generated by @ic-reactor/codegen") &&
59
- content.includes("createActorHooks(") &&
60
- !isStableWrapperFile(content)
45
+ content.includes("createActorHooks(")
61
46
  )
62
47
  }
63
48
 
@@ -137,48 +122,37 @@ export async function runCanisterPipeline(
137
122
  }
138
123
  }
139
124
 
140
- // ── Step 2: Reactor implementation + stable wrapper ───────────────────────
125
+ // ── Step 2: Reactor file ───────────────────────────────────────────────────
141
126
 
142
- const generatedReactorPath = path.join(canisterOutDir, "index.generated.ts")
143
- const wrapperPath = path.join(canisterOutDir, "index.ts")
144
- const reactorMode = resolveReactorMode(name, globalConfig)
127
+ const reactorPath = path.join(canisterOutDir, "index.ts")
128
+ const reactorClass = resolveReactorClass(canisterConfig)
145
129
 
146
130
  try {
147
131
  const reactorContent = generateReactorFile({
148
132
  canisterName: name,
149
133
  didFile: resolvedDidFile,
150
134
  clientManagerPath: resolvedClientManagerPath,
151
- reactorMode,
152
- })
153
- const wrapperContent = generateReactorWrapperFile({
154
- canisterName: name,
155
- didFile: resolvedDidFile,
156
- clientManagerPath: resolvedClientManagerPath,
157
- reactorMode,
135
+ reactorClass,
158
136
  })
159
137
 
160
138
  fs.mkdirSync(canisterOutDir, { recursive: true })
161
- fs.writeFileSync(generatedReactorPath, reactorContent)
162
-
163
- files.push({ success: true, filePath: generatedReactorPath })
164
-
165
- if (!fs.existsSync(wrapperPath)) {
166
- fs.writeFileSync(wrapperPath, wrapperContent)
167
- files.push({ success: true, filePath: wrapperPath })
139
+ if (!fs.existsSync(reactorPath)) {
140
+ fs.writeFileSync(reactorPath, reactorContent)
141
+ files.push({ success: true, filePath: reactorPath })
168
142
  } else {
169
- const existingWrapperContent = fs.readFileSync(wrapperPath, "utf-8")
143
+ const existingIndexContent = fs.readFileSync(reactorPath, "utf-8")
170
144
 
171
- if (isLegacyGeneratedIndexFile(existingWrapperContent)) {
172
- fs.writeFileSync(wrapperPath, wrapperContent)
173
- files.push({ success: true, filePath: wrapperPath })
145
+ if (isGeneratedIndexFile(existingIndexContent)) {
146
+ fs.writeFileSync(reactorPath, reactorContent)
147
+ files.push({ success: true, filePath: reactorPath })
174
148
  } else {
175
- files.push({ success: true, filePath: wrapperPath, skipped: true })
149
+ files.push({ success: true, filePath: reactorPath, skipped: true })
176
150
  }
177
151
  }
178
152
  } catch (err) {
179
153
  files.push({
180
154
  success: false,
181
- filePath: generatedReactorPath,
155
+ filePath: reactorPath,
182
156
  error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`,
183
157
  })
184
158
 
@@ -1,42 +1,45 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { generateReactorFile, generateReactorWrapperFile } from "./generators"
2
+ import { generateReactorFile } from "./generators"
3
3
 
4
4
  describe("Reactor generator", () => {
5
- it("keeps default behavior as display mode", () => {
5
+ it("keeps default behavior as DisplayReactor", () => {
6
6
  const content = generateReactorFile({
7
7
  canisterName: "backend",
8
8
  didFile: "mock/backend.did",
9
9
  clientManagerPath: "../../clients",
10
10
  })
11
11
 
12
- expect(content).toMatchSnapshot("display-mode-index-generated")
12
+ expect(content).toMatchSnapshot("display-reactor-index")
13
13
  expect(content).toContain("new DisplayReactor<BackendService>")
14
- expect(content).toContain(
15
- 'export const BackendReactorMode = "display" as const'
16
- )
14
+ expect(content).not.toContain("export const BackendReactorMode")
17
15
  })
18
16
 
19
- it("supports raw mode generation", () => {
17
+ it("supports Reactor mode generation", () => {
20
18
  const content = generateReactorFile({
21
19
  canisterName: "workflow_engine",
22
20
  didFile: "mock/workflow_engine.did",
23
- reactorMode: "raw",
21
+ reactorClass: "Reactor",
24
22
  })
25
23
 
26
- expect(content).toMatchSnapshot("raw-mode-index-generated")
24
+ expect(content).toMatchSnapshot("reactor-index")
27
25
  expect(content).toContain("new Reactor<WorkflowEngineService>")
28
- expect(content).toContain(
29
- 'export const WorkflowEngineReactorMode = "raw" as const'
30
- )
26
+ expect(content).not.toContain("createWorkflowEngineDisplayReactor")
31
27
  })
32
28
 
33
- it("generates a stable wrapper file", () => {
34
- const content = generateReactorWrapperFile({
35
- canisterName: "backend",
36
- didFile: "mock/backend.did",
29
+ it("supports candid reactor subclasses", () => {
30
+ const content = generateReactorFile({
31
+ canisterName: "ledger",
32
+ didFile: "mock/ledger.did",
33
+ reactorClass: "MetadataDisplayReactor",
37
34
  })
38
35
 
39
- expect(content).toMatchSnapshot("stable-wrapper-index")
40
- expect(content).toContain('export * from "./index.generated"')
36
+ expect(content).toMatchSnapshot("metadata-display-reactor-index")
37
+ expect(content).toContain(
38
+ 'import { createActorHooks } from "@ic-reactor/react"'
39
+ )
40
+ expect(content).toContain(
41
+ 'import { MetadataDisplayReactor } from "@ic-reactor/candid"'
42
+ )
43
+ expect(content).toContain("new MetadataDisplayReactor<LedgerService>")
41
44
  })
42
45
  })
package/src/types.ts CHANGED
@@ -24,24 +24,21 @@ export interface CanisterConfig {
24
24
  * Example: "../../clients" → `import { clientManager } from "../../clients"`
25
25
  */
26
26
  clientManagerPath?: string
27
+ /**
28
+ * Reactor class used for generated hooks in this canister.
29
+ * Defaults to DisplayReactor for backward compatibility.
30
+ */
31
+ mode?: ReactorClassName
27
32
  /** Optional fixed canister ID */
28
33
  canisterId?: string
29
34
  }
30
35
 
31
- export type ReactorMode = "raw" | "display"
32
-
33
- export interface ReactorGenerationConfig {
34
- /**
35
- * Default reactor mode used for generated hook exports.
36
- * `display` preserves current behavior.
37
- */
38
- defaultMode?: ReactorMode
39
- /**
40
- * Optional per-canister overrides keyed by canister name.
41
- * Example: { workflow_engine: "raw" }
42
- */
43
- canisters?: Record<string, ReactorMode>
44
- }
36
+ export type ReactorClassName =
37
+ | "Reactor"
38
+ | "DisplayReactor"
39
+ | "CandidReactor"
40
+ | "CandidDisplayReactor"
41
+ | "MetadataDisplayReactor"
45
42
 
46
43
  /**
47
44
  * Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
@@ -59,8 +56,6 @@ export interface CodegenConfig {
59
56
  * Individual canisters can override via `CanisterConfig.clientManagerPath`.
60
57
  */
61
58
  clientManagerPath?: string
62
- /** Optional reactor mode generation settings */
63
- reactor?: ReactorGenerationConfig
64
59
  /** Canister configurations, keyed by canister name */
65
60
  canisters: Record<string, CanisterConfig>
66
61
  }