@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 +10 -56
- package/dist/index.cjs +36 -66
- package/dist/index.d.cts +13 -27
- package/dist/index.d.ts +13 -27
- package/dist/index.js +36 -65
- package/package.json +1 -1
- package/src/__snapshots__/reactor.test.ts.snap +47 -53
- package/src/generators/index.ts +1 -1
- package/src/generators/reactor.ts +28 -50
- package/src/index.ts +1 -2
- package/src/pipeline.test.ts +21 -36
- package/src/pipeline.ts +21 -47
- package/src/reactor.test.ts +21 -18
- package/src/types.ts +11 -16
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
|
|
31
|
+
## Reactor Class Configuration
|
|
37
32
|
|
|
38
|
-
`
|
|
33
|
+
Set `canisterConfig.mode` to choose the generated reactor class:
|
|
39
34
|
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
35
|
+
- `DisplayReactor` (default)
|
|
36
|
+
- `Reactor`
|
|
37
|
+
- `CandidReactor`
|
|
38
|
+
- `CandidDisplayReactor`
|
|
39
|
+
- `MetadataDisplayReactor`
|
|
42
40
|
|
|
43
|
-
`
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
199
|
-
return
|
|
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
|
|
205
|
-
return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(")
|
|
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
|
|
246
|
-
const
|
|
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
|
-
|
|
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.
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
269
|
-
if (
|
|
270
|
-
import_node_fs2.default.writeFileSync(
|
|
271
|
-
files.push({ success: true, filePath:
|
|
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:
|
|
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:
|
|
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
|
-
*
|
|
34
|
-
*
|
|
23
|
+
* Reactor class used for generated hooks in this canister.
|
|
24
|
+
* Defaults to DisplayReactor for backward compatibility.
|
|
35
25
|
*/
|
|
36
|
-
|
|
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
|
|
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"
|
|
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
|
|
238
|
-
* Default: "
|
|
228
|
+
* Which reactor class should back the generated hooks.
|
|
229
|
+
* Default: "DisplayReactor" (backward compatible)
|
|
239
230
|
*/
|
|
240
|
-
|
|
231
|
+
reactorClass?: ReactorClassName;
|
|
241
232
|
}
|
|
242
233
|
/**
|
|
243
|
-
* Generate the content of a canister's `index.
|
|
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
|
|
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
|
-
*
|
|
34
|
-
*
|
|
23
|
+
* Reactor class used for generated hooks in this canister.
|
|
24
|
+
* Defaults to DisplayReactor for backward compatibility.
|
|
35
25
|
*/
|
|
36
|
-
|
|
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
|
|
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"
|
|
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
|
|
238
|
-
* Default: "
|
|
228
|
+
* Which reactor class should back the generated hooks.
|
|
229
|
+
* Default: "DisplayReactor" (backward compatible)
|
|
239
230
|
*/
|
|
240
|
-
|
|
231
|
+
reactorClass?: ReactorClassName;
|
|
241
232
|
}
|
|
242
233
|
/**
|
|
243
|
-
* Generate the content of a canister's `index.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
153
|
-
return
|
|
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
|
|
159
|
-
return content.includes("Auto-generated by @ic-reactor/codegen") && content.includes("createActorHooks(")
|
|
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
|
|
200
|
-
const
|
|
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
|
-
|
|
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.
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
223
|
-
if (
|
|
224
|
-
fs2.writeFileSync(
|
|
225
|
-
files.push({ success: true, filePath:
|
|
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:
|
|
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:
|
|
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,18 +1,8 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`Reactor generator >
|
|
4
|
-
"
|
|
5
|
-
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
59
|
-
"import {
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
`;
|
package/src/generators/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type {
|
|
|
11
11
|
DeclarationsGeneratorResult,
|
|
12
12
|
} from "./declarations.js"
|
|
13
13
|
|
|
14
|
-
export { generateReactorFile
|
|
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 {
|
|
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
|
|
42
|
-
* Default: "
|
|
41
|
+
* Which reactor class should back the generated hooks.
|
|
42
|
+
* Default: "DisplayReactor" (backward compatible)
|
|
43
43
|
*/
|
|
44
|
-
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
package/src/pipeline.test.ts
CHANGED
|
@@ -31,7 +31,7 @@ describe("Codegen pipeline", () => {
|
|
|
31
31
|
)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
it("
|
|
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
|
|
53
|
+
const indexPath = path.join(
|
|
59
54
|
projectRoot,
|
|
60
|
-
"src/declarations/workflow_engine/index.
|
|
55
|
+
"src/declarations/workflow_engine/index.ts"
|
|
61
56
|
)
|
|
62
|
-
const generated = fs.readFileSync(
|
|
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
|
|
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
|
|
82
|
+
const indexPath = path.join(
|
|
90
83
|
projectRoot,
|
|
91
84
|
"src/declarations/backend/index.ts"
|
|
92
85
|
)
|
|
93
86
|
fs.writeFileSync(
|
|
94
|
-
|
|
95
|
-
`// user
|
|
96
|
-
export const
|
|
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(
|
|
104
|
-
expect(wrapper).toContain("
|
|
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:
|
|
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("
|
|
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
|
|
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
|
|
165
|
-
const
|
|
166
|
-
expect(
|
|
167
|
-
expect(
|
|
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
|
|
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
|
-
|
|
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"
|
|
35
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
function
|
|
42
|
-
|
|
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
|
|
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
|
|
125
|
+
// ── Step 2: Reactor file ───────────────────────────────────────────────────
|
|
141
126
|
|
|
142
|
-
const
|
|
143
|
-
const
|
|
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
|
-
|
|
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.
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
143
|
+
const existingIndexContent = fs.readFileSync(reactorPath, "utf-8")
|
|
170
144
|
|
|
171
|
-
if (
|
|
172
|
-
fs.writeFileSync(
|
|
173
|
-
files.push({ success: true, filePath:
|
|
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:
|
|
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:
|
|
155
|
+
filePath: reactorPath,
|
|
182
156
|
error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
183
157
|
})
|
|
184
158
|
|
package/src/reactor.test.ts
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { generateReactorFile
|
|
2
|
+
import { generateReactorFile } from "./generators"
|
|
3
3
|
|
|
4
4
|
describe("Reactor generator", () => {
|
|
5
|
-
it("keeps default behavior as
|
|
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-
|
|
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
|
|
17
|
+
it("supports Reactor mode generation", () => {
|
|
20
18
|
const content = generateReactorFile({
|
|
21
19
|
canisterName: "workflow_engine",
|
|
22
20
|
didFile: "mock/workflow_engine.did",
|
|
23
|
-
|
|
21
|
+
reactorClass: "Reactor",
|
|
24
22
|
})
|
|
25
23
|
|
|
26
|
-
expect(content).toMatchSnapshot("
|
|
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("
|
|
34
|
-
const content =
|
|
35
|
-
canisterName: "
|
|
36
|
-
didFile: "mock/
|
|
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("
|
|
40
|
-
expect(content).toContain(
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
}
|