@ic-reactor/codegen 0.11.1 → 0.12.1
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 +4 -4
- package/dist/index.cjs +238 -22
- package/dist/index.d.cts +111 -1
- package/dist/index.d.ts +111 -1
- package/dist/index.js +228 -22
- package/llms.txt +28 -0
- package/package.json +21 -12
- package/src/__snapshots__/reactor.test.ts.snap +12 -0
- package/src/bindgen.test.ts +1 -1
- package/src/generators/client.ts +0 -1
- package/src/generators/reactor.ts +27 -4
- package/src/index.ts +19 -0
- package/src/naming.test.ts +1 -1
- package/src/pipeline.test.ts +1 -1
- package/src/pipeline.ts +36 -9
- package/src/reactor.test.ts +4 -1
- package/src/validate.test.ts +393 -0
- package/src/validate.ts +369 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/pipeline.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs3 from "fs";
|
|
3
|
+
import path4 from "path";
|
|
4
4
|
|
|
5
5
|
// src/generators/declarations.ts
|
|
6
6
|
import { didToJs, didToTs } from "@ic-reactor/parser";
|
|
@@ -76,6 +76,9 @@ function getReactorName(canisterName) {
|
|
|
76
76
|
function getServiceTypeName(canisterName) {
|
|
77
77
|
return `${toPascalCase(canisterName)}Service`;
|
|
78
78
|
}
|
|
79
|
+
function getHookPrefix(canisterName) {
|
|
80
|
+
return toPascalCase(canisterName);
|
|
81
|
+
}
|
|
79
82
|
|
|
80
83
|
// src/generators/reactor.ts
|
|
81
84
|
function getReactorClassImportSource(reactorClass, runtimeTarget) {
|
|
@@ -87,6 +90,10 @@ function getReactorClassImportSource(reactorClass, runtimeTarget) {
|
|
|
87
90
|
case "CandidDisplayReactor":
|
|
88
91
|
case "MetadataDisplayReactor":
|
|
89
92
|
return "@ic-reactor/candid";
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Unknown reactor class ${JSON.stringify(reactorClass)}. Expected one of: Reactor, DisplayReactor, CandidReactor, CandidDisplayReactor, MetadataDisplayReactor.`
|
|
96
|
+
);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
function generateReactorFile(options) {
|
|
@@ -120,9 +127,9 @@ export const {
|
|
|
120
127
|
useActorMethod: use${pascalName}Method,
|
|
121
128
|
} = createActorHooks(${reactorName})
|
|
122
129
|
` : "";
|
|
123
|
-
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from
|
|
124
|
-
import { clientManager } from
|
|
125
|
-
import { idlFactory, type _SERVICE } from
|
|
130
|
+
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from ${JSON.stringify(reactorImportSource)}
|
|
131
|
+
import { clientManager } from ${JSON.stringify(clientManagerPath)}
|
|
132
|
+
import { idlFactory, type _SERVICE } from ${JSON.stringify(declarationsPath)}
|
|
126
133
|
|
|
127
134
|
export type ${serviceName} = _SERVICE
|
|
128
135
|
|
|
@@ -131,11 +138,14 @@ export type ${serviceName} = _SERVICE
|
|
|
131
138
|
*
|
|
132
139
|
* Auto-generated by @ic-reactor/codegen \u2014 do not edit.
|
|
133
140
|
* This file is overwritten whenever generation runs.
|
|
141
|
+
*
|
|
142
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
143
|
+
* factory modules). Avoid editing this managed file directly.
|
|
134
144
|
*/
|
|
135
145
|
export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
136
146
|
clientManager,
|
|
137
147
|
idlFactory,
|
|
138
|
-
${canisterIdLine} name:
|
|
148
|
+
${canisterIdLine} name: ${JSON.stringify(canisterName)},
|
|
139
149
|
})${hookExports || "\n"}`;
|
|
140
150
|
}
|
|
141
151
|
function generateReactorEntryFile() {
|
|
@@ -144,11 +154,176 @@ function generateReactorEntryFile() {
|
|
|
144
154
|
*
|
|
145
155
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
146
156
|
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
157
|
+
*
|
|
158
|
+
* Recommended customization points:
|
|
159
|
+
* - define reusable query/mutation factories
|
|
160
|
+
* - add app-specific hooks and cache invalidation wiring
|
|
161
|
+
* - compose generated APIs into route loaders/actions
|
|
162
|
+
*
|
|
163
|
+
* Do not edit \`index.generated.ts\`; it is regenerated on each codegen run.
|
|
164
|
+
* AI guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
165
|
+
* Skill install: npx skills add B3Pay/ic-reactor-skills --full-depth --skill ic-reactor-hooks
|
|
147
166
|
*/
|
|
148
167
|
export * from "./index.generated"
|
|
149
168
|
`;
|
|
150
169
|
}
|
|
151
170
|
|
|
171
|
+
// src/validate.ts
|
|
172
|
+
import fs2 from "fs";
|
|
173
|
+
import path3 from "path";
|
|
174
|
+
var CodegenConfigError = class extends Error {
|
|
175
|
+
name = "CodegenConfigError";
|
|
176
|
+
constructor(message) {
|
|
177
|
+
super(message);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var CANISTER_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
181
|
+
var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
182
|
+
var MAX_CANISTER_NAME_LENGTH = 64;
|
|
183
|
+
var HAS_URI_SCHEME = /^(?:[A-Za-z][A-Za-z0-9+.-]*:|\/\/)/;
|
|
184
|
+
var BREAKS_OUT_OF_LITERAL = /["'`\\\u0000-\u001f\u2028\u2029]/;
|
|
185
|
+
function assertSafeCanisterName(name) {
|
|
186
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
187
|
+
throw new CodegenConfigError(
|
|
188
|
+
`Invalid canister name: expected a non-empty string, received ${name === void 0 ? "undefined" : JSON.stringify(name)}. Check the "canisters" entries in your ic-reactor.json (or the plugin's "canisters" option).`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (name.length > MAX_CANISTER_NAME_LENGTH) {
|
|
192
|
+
throw new CodegenConfigError(
|
|
193
|
+
`Invalid canister name ${JSON.stringify(name)}: must be at most ${MAX_CANISTER_NAME_LENGTH} characters.`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (!CANISTER_NAME_PATTERN.test(name)) {
|
|
197
|
+
throw new CodegenConfigError(
|
|
198
|
+
`Invalid canister name ${JSON.stringify(name)}: may contain only letters, digits, "_", "." and "-" (${CANISTER_NAME_PATTERN.source}). Canister names become directory names, so path separators, quotes and whitespace are not allowed.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (name === "." || name === "..") {
|
|
202
|
+
throw new CodegenConfigError(
|
|
203
|
+
`Invalid canister name ${JSON.stringify(name)}: refers to a directory, not a canister.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (toPascalCase(name).length === 0) {
|
|
207
|
+
throw new CodegenConfigError(
|
|
208
|
+
`Invalid canister name ${JSON.stringify(name)}: contains no letters or digits, so it collapses to an empty identifier in generated code.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const derived = [
|
|
212
|
+
["reactor constant", getReactorName(name)],
|
|
213
|
+
["service type", getServiceTypeName(name)],
|
|
214
|
+
["hook name", `use${getHookPrefix(name)}Query`]
|
|
215
|
+
];
|
|
216
|
+
for (const [what, identifier] of derived) {
|
|
217
|
+
if (!IDENTIFIER_PATTERN.test(identifier)) {
|
|
218
|
+
throw new CodegenConfigError(
|
|
219
|
+
`Invalid canister name ${JSON.stringify(name)}: it derives the ${what} ${JSON.stringify(identifier)}, which is not a valid TypeScript identifier. Rename the canister so it does not begin with a digit.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function assertSafeModuleSpecifier(label, specifier) {
|
|
225
|
+
if (typeof specifier !== "string" || specifier.length === 0) {
|
|
226
|
+
throw new CodegenConfigError(
|
|
227
|
+
`Invalid ${label}: expected a non-empty string, received ${specifier === void 0 ? "undefined" : JSON.stringify(specifier)}.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (BREAKS_OUT_OF_LITERAL.test(specifier)) {
|
|
231
|
+
throw new CodegenConfigError(
|
|
232
|
+
`Invalid ${label} ${JSON.stringify(specifier)}: must not contain quotes, backslashes or control characters.`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (/^\s|\s$/.test(specifier)) {
|
|
236
|
+
throw new CodegenConfigError(
|
|
237
|
+
`Invalid ${label} ${JSON.stringify(specifier)}: must not begin or end with whitespace.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (HAS_URI_SCHEME.test(specifier.trim())) {
|
|
241
|
+
throw new CodegenConfigError(
|
|
242
|
+
`Invalid ${label} ${JSON.stringify(specifier)}: must be a relative path ("./\u2026", "../\u2026") or a bare package name. URLs are not allowed \u2014 the generated file imports from this specifier.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
var REACTOR_CLASS_NAMES = [
|
|
247
|
+
"Reactor",
|
|
248
|
+
"DisplayReactor",
|
|
249
|
+
"CandidReactor",
|
|
250
|
+
"CandidDisplayReactor",
|
|
251
|
+
"MetadataDisplayReactor"
|
|
252
|
+
];
|
|
253
|
+
var CODEGEN_TARGETS = ["react", "core"];
|
|
254
|
+
function assertOneOf(label, value, allowed) {
|
|
255
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
256
|
+
throw new CodegenConfigError(
|
|
257
|
+
`Invalid ${label} ${JSON.stringify(value)}: must be one of ${allowed.map((a) => JSON.stringify(a)).join(", ")}.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function realpathAllowingMissing(target) {
|
|
262
|
+
let current = path3.resolve(target);
|
|
263
|
+
const missing = [];
|
|
264
|
+
for (; ; ) {
|
|
265
|
+
try {
|
|
266
|
+
return path3.join(fs2.realpathSync(current), ...missing);
|
|
267
|
+
} catch {
|
|
268
|
+
const parent = path3.dirname(current);
|
|
269
|
+
if (parent === current) return path3.resolve(target);
|
|
270
|
+
missing.unshift(path3.basename(current));
|
|
271
|
+
current = parent;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function assertContainedPath(label, resolved, projectRoot, original = resolved) {
|
|
276
|
+
const relative = path3.relative(
|
|
277
|
+
realpathAllowingMissing(projectRoot),
|
|
278
|
+
realpathAllowingMissing(resolved)
|
|
279
|
+
);
|
|
280
|
+
const [firstSegment] = relative.split(/[\\/]/);
|
|
281
|
+
if (firstSegment === ".." || path3.isAbsolute(relative)) {
|
|
282
|
+
throw new CodegenConfigError(
|
|
283
|
+
`Invalid ${label} ${JSON.stringify(original)}: resolves to ${JSON.stringify(resolved)}, which is outside the project root ${JSON.stringify(path3.resolve(projectRoot))}. Generated output must stay inside the project \u2014 generated directories are deleted and rewritten on every run.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function resolveContainedOutDir(label, outDir, projectRoot) {
|
|
288
|
+
if (typeof outDir !== "string" || outDir.length === 0) {
|
|
289
|
+
throw new CodegenConfigError(
|
|
290
|
+
`Invalid ${label}: expected a non-empty string, received ${outDir === void 0 ? "undefined" : JSON.stringify(outDir)}.`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const resolved = path3.isAbsolute(outDir) ? path3.resolve(outDir) : path3.resolve(projectRoot, outDir);
|
|
294
|
+
assertContainedPath(label, resolved, projectRoot, outDir);
|
|
295
|
+
return resolved;
|
|
296
|
+
}
|
|
297
|
+
function assertSafeCanisterConfig(options) {
|
|
298
|
+
const {
|
|
299
|
+
name,
|
|
300
|
+
canisterOutDir,
|
|
301
|
+
globalOutDir,
|
|
302
|
+
clientManagerPath,
|
|
303
|
+
projectRoot,
|
|
304
|
+
mode,
|
|
305
|
+
target
|
|
306
|
+
} = options;
|
|
307
|
+
assertSafeCanisterName(name);
|
|
308
|
+
assertSafeModuleSpecifier("clientManagerPath", clientManagerPath);
|
|
309
|
+
if (mode != null) assertOneOf("mode", mode, REACTOR_CLASS_NAMES);
|
|
310
|
+
if (target != null) assertOneOf("target", target, CODEGEN_TARGETS);
|
|
311
|
+
const outDir = canisterOutDir != null ? resolveContainedOutDir(
|
|
312
|
+
`outDir for canister ${JSON.stringify(name)}`,
|
|
313
|
+
canisterOutDir,
|
|
314
|
+
projectRoot
|
|
315
|
+
) : path3.join(
|
|
316
|
+
resolveContainedOutDir("outDir", globalOutDir, projectRoot),
|
|
317
|
+
name
|
|
318
|
+
);
|
|
319
|
+
assertContainedPath(
|
|
320
|
+
`output directory for canister ${JSON.stringify(name)}`,
|
|
321
|
+
outDir,
|
|
322
|
+
projectRoot
|
|
323
|
+
);
|
|
324
|
+
return { name, outDir, clientManagerPath };
|
|
325
|
+
}
|
|
326
|
+
|
|
152
327
|
// src/pipeline.ts
|
|
153
328
|
function resolveReactorClass(canisterConfig) {
|
|
154
329
|
return canisterConfig.mode ?? "DisplayReactor";
|
|
@@ -174,8 +349,30 @@ async function runCanisterPipeline(options) {
|
|
|
174
349
|
} = options;
|
|
175
350
|
const { name, didFile, clientManagerPath } = canisterConfig;
|
|
176
351
|
const files = [];
|
|
177
|
-
|
|
178
|
-
|
|
352
|
+
let validated;
|
|
353
|
+
try {
|
|
354
|
+
validated = assertSafeCanisterConfig({
|
|
355
|
+
name,
|
|
356
|
+
canisterOutDir: canisterConfig.outDir,
|
|
357
|
+
globalOutDir: globalConfig.outDir,
|
|
358
|
+
clientManagerPath: clientManagerPath ?? globalConfig.clientManagerPath ?? "../../clients",
|
|
359
|
+
projectRoot,
|
|
360
|
+
mode: canisterConfig.mode,
|
|
361
|
+
target: canisterConfig.target ?? globalConfig.target
|
|
362
|
+
});
|
|
363
|
+
} catch (err) {
|
|
364
|
+
if (err instanceof CodegenConfigError) {
|
|
365
|
+
return {
|
|
366
|
+
canisterName: typeof name === "string" ? name : String(name),
|
|
367
|
+
success: false,
|
|
368
|
+
files,
|
|
369
|
+
error: err.message
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
throw err;
|
|
373
|
+
}
|
|
374
|
+
const resolvedDidFile = path4.isAbsolute(didFile) ? didFile : path4.resolve(projectRoot, didFile);
|
|
375
|
+
if (!fs3.existsSync(resolvedDidFile)) {
|
|
179
376
|
return {
|
|
180
377
|
canisterName: name,
|
|
181
378
|
success: false,
|
|
@@ -183,8 +380,8 @@ async function runCanisterPipeline(options) {
|
|
|
183
380
|
error: `DID file not found: ${resolvedDidFile}`
|
|
184
381
|
};
|
|
185
382
|
}
|
|
186
|
-
const canisterOutDir =
|
|
187
|
-
const resolvedClientManagerPath =
|
|
383
|
+
const canisterOutDir = validated.outDir;
|
|
384
|
+
const resolvedClientManagerPath = validated.clientManagerPath;
|
|
188
385
|
try {
|
|
189
386
|
const declResult = await generateDeclarations({
|
|
190
387
|
didFile: resolvedDidFile,
|
|
@@ -215,8 +412,8 @@ async function runCanisterPipeline(options) {
|
|
|
215
412
|
files
|
|
216
413
|
};
|
|
217
414
|
}
|
|
218
|
-
const reactorPath =
|
|
219
|
-
const entryPath =
|
|
415
|
+
const reactorPath = path4.join(canisterOutDir, "index.generated.ts");
|
|
416
|
+
const entryPath = path4.join(canisterOutDir, "index.ts");
|
|
220
417
|
const reactorClass = resolveReactorClass(canisterConfig);
|
|
221
418
|
const runtimeTarget = resolveRuntimeTarget(canisterConfig, globalConfig);
|
|
222
419
|
try {
|
|
@@ -229,16 +426,16 @@ async function runCanisterPipeline(options) {
|
|
|
229
426
|
reactorClass
|
|
230
427
|
});
|
|
231
428
|
const entryContent = generateReactorEntryFile();
|
|
232
|
-
|
|
233
|
-
|
|
429
|
+
fs3.mkdirSync(canisterOutDir, { recursive: true });
|
|
430
|
+
fs3.writeFileSync(reactorPath, reactorContent);
|
|
234
431
|
files.push({ success: true, filePath: reactorPath });
|
|
235
|
-
if (!
|
|
236
|
-
|
|
432
|
+
if (!fs3.existsSync(entryPath)) {
|
|
433
|
+
fs3.writeFileSync(entryPath, entryContent);
|
|
237
434
|
files.push({ success: true, filePath: entryPath });
|
|
238
435
|
} else {
|
|
239
|
-
const existingEntryContent =
|
|
436
|
+
const existingEntryContent = fs3.readFileSync(entryPath, "utf-8");
|
|
240
437
|
if (isLegacyGeneratedIndexFile(existingEntryContent) || isManagedEntryWrapper(existingEntryContent, entryContent)) {
|
|
241
|
-
|
|
438
|
+
fs3.writeFileSync(entryPath, entryContent);
|
|
242
439
|
files.push({ success: true, filePath: entryPath });
|
|
243
440
|
} else {
|
|
244
441
|
files.push({ success: true, filePath: entryPath, skipped: true });
|
|
@@ -266,7 +463,7 @@ async function runCanisterPipeline(options) {
|
|
|
266
463
|
|
|
267
464
|
// src/parser.ts
|
|
268
465
|
import { didToJs as didToJs2 } from "@ic-reactor/parser";
|
|
269
|
-
import
|
|
466
|
+
import fs4 from "fs";
|
|
270
467
|
function extractMethods(didContent) {
|
|
271
468
|
try {
|
|
272
469
|
const jsContent = didToJs2(didContent);
|
|
@@ -291,10 +488,10 @@ function extractMethods(didContent) {
|
|
|
291
488
|
}
|
|
292
489
|
}
|
|
293
490
|
function parseDIDFile(didFilePath) {
|
|
294
|
-
if (!
|
|
491
|
+
if (!fs4.existsSync(didFilePath)) {
|
|
295
492
|
throw new Error(`DID file not found: ${didFilePath}`);
|
|
296
493
|
}
|
|
297
|
-
const content =
|
|
494
|
+
const content = fs4.readFileSync(didFilePath, "utf-8");
|
|
298
495
|
return extractMethods(content);
|
|
299
496
|
}
|
|
300
497
|
|
|
@@ -315,11 +512,19 @@ ${queryClientImport}
|
|
|
315
512
|
*/
|
|
316
513
|
export const clientManager = new ClientManager({
|
|
317
514
|
queryClient,
|
|
318
|
-
withCanisterEnv: true,
|
|
319
515
|
})
|
|
320
516
|
`;
|
|
321
517
|
}
|
|
322
518
|
export {
|
|
519
|
+
CANISTER_NAME_PATTERN,
|
|
520
|
+
CODEGEN_TARGETS,
|
|
521
|
+
CodegenConfigError,
|
|
522
|
+
REACTOR_CLASS_NAMES,
|
|
523
|
+
assertContainedPath,
|
|
524
|
+
assertOneOf,
|
|
525
|
+
assertSafeCanisterConfig,
|
|
526
|
+
assertSafeCanisterName,
|
|
527
|
+
assertSafeModuleSpecifier,
|
|
323
528
|
declarationsExist,
|
|
324
529
|
extractMethods,
|
|
325
530
|
generateClientFile,
|
|
@@ -329,6 +534,7 @@ export {
|
|
|
329
534
|
getReactorName,
|
|
330
535
|
getServiceTypeName,
|
|
331
536
|
parseDIDFile,
|
|
537
|
+
resolveContainedOutDir,
|
|
332
538
|
runCanisterPipeline,
|
|
333
539
|
toPascalCase
|
|
334
540
|
};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @ic-reactor/codegen — AI Quick Guide
|
|
2
|
+
|
|
3
|
+
Shared generation pipeline used by `@ic-reactor/cli` and
|
|
4
|
+
`@ic-reactor/vite-plugin`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Most apps should install CLI or Vite plugin instead of this package directly.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add -D @ic-reactor/codegen
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Main Use Case
|
|
15
|
+
|
|
16
|
+
Use `runCanisterPipeline(...)` when you need programmatic generation in custom
|
|
17
|
+
tooling/automation.
|
|
18
|
+
|
|
19
|
+
## Generated File Model
|
|
20
|
+
|
|
21
|
+
- `index.generated.ts` is managed output
|
|
22
|
+
- `index.ts` is a stable wrapper
|
|
23
|
+
- app-specific logic should live in wrappers or adjacent modules
|
|
24
|
+
|
|
25
|
+
## Full References
|
|
26
|
+
|
|
27
|
+
- Full AI guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
28
|
+
- Codegen docs: https://ic-reactor.b3pay.net/v3/packages/codegen
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Shared code generation utilities for IC Reactor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -8,14 +8,22 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
15
21
|
},
|
|
16
22
|
"files": [
|
|
17
23
|
"dist",
|
|
18
|
-
"src"
|
|
24
|
+
"src",
|
|
25
|
+
"README.md",
|
|
26
|
+
"llms.txt"
|
|
19
27
|
],
|
|
20
28
|
"keywords": [
|
|
21
29
|
"internet-computer",
|
|
@@ -32,21 +40,22 @@
|
|
|
32
40
|
"url": "git+https://github.com/B3Pay/ic-reactor.git",
|
|
33
41
|
"directory": "packages/codegen"
|
|
34
42
|
},
|
|
43
|
+
"homepage": "https://ic-reactor.b3pay.net/v3/packages/codegen",
|
|
35
44
|
"dependencies": {
|
|
36
45
|
"change-case": "^5.4.4",
|
|
37
|
-
"@ic-reactor/parser": "0.4.
|
|
46
|
+
"@ic-reactor/parser": "0.4.7"
|
|
38
47
|
},
|
|
39
48
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^
|
|
49
|
+
"@types/node": "^26.1.1",
|
|
41
50
|
"tsup": "^8.5.1",
|
|
42
|
-
"typescript": "^
|
|
43
|
-
"vitest": "^4.1.
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vitest": "^4.1.10"
|
|
44
53
|
},
|
|
45
54
|
"scripts": {
|
|
46
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
55
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.json",
|
|
47
56
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
|
|
48
57
|
"test": "vitest run",
|
|
49
58
|
"test:watch": "vitest",
|
|
50
|
-
"typecheck": "tsc --noEmit"
|
|
59
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
51
60
|
}
|
|
52
61
|
}
|
|
@@ -13,6 +13,9 @@ export type BackendService = _SERVICE
|
|
|
13
13
|
*
|
|
14
14
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
15
15
|
* This file is overwritten whenever generation runs.
|
|
16
|
+
*
|
|
17
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
18
|
+
* factory modules). Avoid editing this managed file directly.
|
|
16
19
|
*/
|
|
17
20
|
export const backendReactor = new DisplayReactor<BackendService>({
|
|
18
21
|
clientManager,
|
|
@@ -44,6 +47,9 @@ export type WorkflowEngineService = _SERVICE
|
|
|
44
47
|
*
|
|
45
48
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
46
49
|
* This file is overwritten whenever generation runs.
|
|
50
|
+
*
|
|
51
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
52
|
+
* factory modules). Avoid editing this managed file directly.
|
|
47
53
|
*/
|
|
48
54
|
export const workflowEngineReactor = new Reactor<WorkflowEngineService>({
|
|
49
55
|
clientManager,
|
|
@@ -75,6 +81,9 @@ export type LedgerService = _SERVICE
|
|
|
75
81
|
*
|
|
76
82
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
77
83
|
* This file is overwritten whenever generation runs.
|
|
84
|
+
*
|
|
85
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
86
|
+
* factory modules). Avoid editing this managed file directly.
|
|
78
87
|
*/
|
|
79
88
|
export const ledgerReactor = new MetadataDisplayReactor<LedgerService>({
|
|
80
89
|
clientManager,
|
|
@@ -105,6 +114,9 @@ export type BackendService = _SERVICE
|
|
|
105
114
|
*
|
|
106
115
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
107
116
|
* This file is overwritten whenever generation runs.
|
|
117
|
+
*
|
|
118
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
119
|
+
* factory modules). Avoid editing this managed file directly.
|
|
108
120
|
*/
|
|
109
121
|
export const backendReactor = new DisplayReactor<BackendService>({
|
|
110
122
|
clientManager,
|
package/src/bindgen.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
2
2
|
import fs from "node:fs"
|
|
3
3
|
import path from "node:path"
|
|
4
|
-
import { generateDeclarations } from "./generators"
|
|
4
|
+
import { generateDeclarations } from "./generators/index.js"
|
|
5
5
|
|
|
6
6
|
describe("Bindgen", () => {
|
|
7
7
|
const mockDidFile = "mock/test.did"
|
package/src/generators/client.ts
CHANGED
|
@@ -60,6 +60,14 @@ function getReactorClassImportSource(
|
|
|
60
60
|
case "CandidDisplayReactor":
|
|
61
61
|
case "MetadataDisplayReactor":
|
|
62
62
|
return "@ic-reactor/candid"
|
|
63
|
+
default:
|
|
64
|
+
// The pipeline validates `mode` against a closed set before we are
|
|
65
|
+
// reached. Failing closed here means a caller that skips validation gets
|
|
66
|
+
// an error rather than an unknown class name interpolated into source.
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Unknown reactor class ${JSON.stringify(reactorClass)}. Expected one of: ` +
|
|
69
|
+
`Reactor, DisplayReactor, CandidReactor, CandidDisplayReactor, MetadataDisplayReactor.`
|
|
70
|
+
)
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -105,9 +113,12 @@ export const {
|
|
|
105
113
|
`
|
|
106
114
|
: ""
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
// Every interpolation into emitted source is JSON.stringify'd. The pipeline
|
|
117
|
+
// validates these values before we are called; quoting them here as well
|
|
118
|
+
// means a future caller that skips validation cannot inject source text.
|
|
119
|
+
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from ${JSON.stringify(reactorImportSource)}
|
|
120
|
+
import { clientManager } from ${JSON.stringify(clientManagerPath)}
|
|
121
|
+
import { idlFactory, type _SERVICE } from ${JSON.stringify(declarationsPath)}
|
|
111
122
|
|
|
112
123
|
export type ${serviceName} = _SERVICE
|
|
113
124
|
|
|
@@ -116,11 +127,14 @@ export type ${serviceName} = _SERVICE
|
|
|
116
127
|
*
|
|
117
128
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
118
129
|
* This file is overwritten whenever generation runs.
|
|
130
|
+
*
|
|
131
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
132
|
+
* factory modules). Avoid editing this managed file directly.
|
|
119
133
|
*/
|
|
120
134
|
export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
121
135
|
clientManager,
|
|
122
136
|
idlFactory,
|
|
123
|
-
${canisterIdLine} name:
|
|
137
|
+
${canisterIdLine} name: ${JSON.stringify(canisterName)},
|
|
124
138
|
})${hookExports || "\n"}`
|
|
125
139
|
}
|
|
126
140
|
|
|
@@ -133,6 +147,15 @@ export function generateReactorEntryFile(): string {
|
|
|
133
147
|
*
|
|
134
148
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
135
149
|
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
150
|
+
*
|
|
151
|
+
* Recommended customization points:
|
|
152
|
+
* - define reusable query/mutation factories
|
|
153
|
+
* - add app-specific hooks and cache invalidation wiring
|
|
154
|
+
* - compose generated APIs into route loaders/actions
|
|
155
|
+
*
|
|
156
|
+
* Do not edit \`index.generated.ts\`; it is regenerated on each codegen run.
|
|
157
|
+
* AI guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
158
|
+
* Skill install: npx skills add B3Pay/ic-reactor-skills --full-depth --skill ic-reactor-hooks
|
|
136
159
|
*/
|
|
137
160
|
export * from "./index.generated"
|
|
138
161
|
`
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,25 @@ export type { PipelineOptions, PipelineResult } from "./pipeline.js"
|
|
|
21
21
|
// Utilities
|
|
22
22
|
export { toPascalCase, getReactorName, getServiceTypeName } from "./naming.js"
|
|
23
23
|
|
|
24
|
+
// Config validation (run by the pipeline; exported for callers that want to
|
|
25
|
+
// validate a config before invoking generation)
|
|
26
|
+
export {
|
|
27
|
+
assertSafeCanisterConfig,
|
|
28
|
+
assertSafeCanisterName,
|
|
29
|
+
assertSafeModuleSpecifier,
|
|
30
|
+
assertContainedPath,
|
|
31
|
+
assertOneOf,
|
|
32
|
+
resolveContainedOutDir,
|
|
33
|
+
CodegenConfigError,
|
|
34
|
+
CANISTER_NAME_PATTERN,
|
|
35
|
+
REACTOR_CLASS_NAMES,
|
|
36
|
+
CODEGEN_TARGETS,
|
|
37
|
+
} from "./validate.js"
|
|
38
|
+
export type {
|
|
39
|
+
ValidatedCanisterPaths,
|
|
40
|
+
ValidateCanisterConfigOptions,
|
|
41
|
+
} from "./validate.js"
|
|
42
|
+
|
|
24
43
|
export { parseDIDFile, extractMethods } from "./parser.js"
|
|
25
44
|
export type { MethodInfo, MethodType } from "./parser.js"
|
|
26
45
|
|
package/src/naming.test.ts
CHANGED
package/src/pipeline.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs"
|
|
|
2
2
|
import os from "node:os"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest"
|
|
5
|
-
import { runCanisterPipeline } from "./pipeline"
|
|
5
|
+
import { runCanisterPipeline } from "./pipeline.js"
|
|
6
6
|
|
|
7
7
|
describe("Codegen pipeline", () => {
|
|
8
8
|
const tempDirs: string[] = []
|