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