@ic-reactor/codegen 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,22 +31,11 @@ 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,
35
- formatMethodForDisplay: () => formatMethodForDisplay,
34
+ generateClientFile: () => generateClientFile,
36
35
  generateDeclarations: () => generateDeclarations,
37
- generateInfiniteQueryHook: () => generateInfiniteQueryHook,
38
- generateMutationHook: () => generateMutationHook,
39
- generateQueryHook: () => generateQueryHook,
40
36
  generateReactorFile: () => generateReactorFile,
41
- getHookExportName: () => getHookExportName,
42
- getHookFileName: () => getHookFileName,
43
- getMethodsByType: () => getMethodsByType,
44
- getReactHookName: () => getReactHookName,
45
37
  getReactorName: () => getReactorName,
46
38
  getServiceTypeName: () => getServiceTypeName,
47
- parseDIDFile: () => parseDIDFile,
48
- saveCandidFile: () => saveCandidFile,
49
- toCamelCase: () => toCamelCase,
50
39
  toPascalCase: () => toPascalCase
51
40
  });
52
41
  module.exports = __toCommonJS(index_exports);
@@ -59,21 +48,6 @@ function toPascalCase(str) {
59
48
  function toCamelCase(str) {
60
49
  return (0, import_change_case.camelCase)(str);
61
50
  }
62
- function getHookFileName(methodName, hookType) {
63
- const camelMethod = toCamelCase(methodName);
64
- const pascalType = toPascalCase(hookType);
65
- return `${camelMethod}${pascalType}.ts`;
66
- }
67
- function getHookExportName(methodName, hookType) {
68
- const camelMethod = toCamelCase(methodName);
69
- const pascalType = toPascalCase(hookType);
70
- return `${camelMethod}${pascalType}`;
71
- }
72
- function getReactHookName(methodName, hookType) {
73
- const pascalMethod = toPascalCase(methodName);
74
- const pascalType = toPascalCase(hookType);
75
- return `use${pascalMethod}${pascalType}`;
76
- }
77
51
  function getReactorName(canisterName) {
78
52
  return `${toCamelCase(canisterName)}Reactor`;
79
53
  }
@@ -81,52 +55,13 @@ function getServiceTypeName(canisterName) {
81
55
  return `${toPascalCase(canisterName)}Service`;
82
56
  }
83
57
 
84
- // src/did.ts
85
- var import_node_fs = __toESM(require("fs"), 1);
86
- function parseDIDFile(didFilePath) {
87
- if (!import_node_fs.default.existsSync(didFilePath)) {
88
- throw new Error(`DID file not found: ${didFilePath}`);
89
- }
90
- const content = import_node_fs.default.readFileSync(didFilePath, "utf-8");
91
- return extractMethods(content);
92
- }
93
- function extractMethods(didContent) {
94
- const methods = [];
95
- const cleanContent = didContent.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
96
- const methodRegex = /([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(?:func\s*)?\(([^)]*)\)\s*->\s*\(([^)]*)\)\s*(query|composite_query)?/g;
97
- let match;
98
- while ((match = methodRegex.exec(cleanContent)) !== null) {
99
- const name = match[1];
100
- const args = match[2].trim();
101
- const returnType = match[3].trim();
102
- const queryAnnotation = match[4];
103
- const isQuery = queryAnnotation === "query" || queryAnnotation === "composite_query";
104
- methods.push({
105
- name,
106
- type: isQuery ? "query" : "mutation",
107
- hasArgs: args.length > 0 && args !== "",
108
- argsDescription: args || void 0,
109
- returnDescription: returnType || void 0
110
- });
111
- }
112
- return methods;
113
- }
114
- function getMethodsByType(methods, type) {
115
- return methods.filter((m) => m.type === type);
116
- }
117
- function formatMethodForDisplay(method) {
118
- const typeLabel = method.type === "query" ? "query" : "update";
119
- const argsLabel = method.hasArgs ? "with args" : "no args";
120
- return `${method.name} (${typeLabel}, ${argsLabel})`;
121
- }
122
-
123
58
  // src/bindgen.ts
124
- var import_core = require("@icp-sdk/bindgen/core");
59
+ var import_parser = require("@ic-reactor/parser");
125
60
  var import_node_path = __toESM(require("path"), 1);
126
- var import_node_fs2 = __toESM(require("fs"), 1);
61
+ var import_node_fs = __toESM(require("fs"), 1);
127
62
  async function generateDeclarations(options) {
128
63
  const { didFile, outDir } = options;
129
- if (!import_node_fs2.default.existsSync(didFile)) {
64
+ if (!import_node_fs.default.existsSync(didFile)) {
130
65
  return {
131
66
  success: false,
132
67
  declarationsDir: "",
@@ -134,27 +69,25 @@ async function generateDeclarations(options) {
134
69
  };
135
70
  }
136
71
  const declarationsDir = import_node_path.default.join(outDir, "declarations");
72
+ const didFileName = import_node_path.default.basename(didFile);
137
73
  try {
138
- if (!import_node_fs2.default.existsSync(outDir)) {
139
- import_node_fs2.default.mkdirSync(outDir, { recursive: true });
74
+ const didContent = import_node_fs.default.readFileSync(didFile, "utf-8");
75
+ if (!import_node_fs.default.existsSync(outDir)) {
76
+ import_node_fs.default.mkdirSync(outDir, { recursive: true });
140
77
  }
141
- if (import_node_fs2.default.existsSync(declarationsDir)) {
142
- import_node_fs2.default.rmSync(declarationsDir, { recursive: true, force: true });
78
+ if (import_node_fs.default.existsSync(declarationsDir)) {
79
+ import_node_fs.default.rmSync(declarationsDir, { recursive: true, force: true });
143
80
  }
144
- import_node_fs2.default.mkdirSync(declarationsDir, { recursive: true });
145
- await (0, import_core.generate)({
146
- didFile,
147
- outDir,
148
- // Pass the parent directory; bindgen appends "declarations"
149
- output: {
150
- actor: {
151
- disabled: true
152
- // We don't need actor creation, we use Reactor
153
- },
154
- force: true
155
- // Overwrite existing files
156
- }
157
- });
81
+ import_node_fs.default.mkdirSync(declarationsDir, { recursive: true });
82
+ const jsContent = (0, import_parser.didToJs)(didContent);
83
+ const tsContent = (0, import_parser.didToTs)(didContent);
84
+ const baseName = didFileName.replace(/\.did$/, "");
85
+ const jsPath = import_node_path.default.join(declarationsDir, baseName + ".js");
86
+ const dtsPath = import_node_path.default.join(declarationsDir, baseName + ".d.ts");
87
+ const didPath = import_node_path.default.join(declarationsDir, didFileName);
88
+ import_node_fs.default.writeFileSync(jsPath, jsContent);
89
+ import_node_fs.default.writeFileSync(dtsPath, tsContent);
90
+ import_node_fs.default.writeFileSync(didPath, didContent);
158
91
  return {
159
92
  success: true,
160
93
  declarationsDir
@@ -169,77 +102,58 @@ async function generateDeclarations(options) {
169
102
  }
170
103
  function declarationsExist(outDir, canisterName) {
171
104
  const declarationsDir = import_node_path.default.join(outDir, "declarations");
172
- const didTsPath = import_node_path.default.join(declarationsDir, `${canisterName}.did.ts`);
173
- return import_node_fs2.default.existsSync(didTsPath);
174
- }
175
- function saveCandidFile(candidSource, outDir, canisterName) {
176
- const candidDir = import_node_path.default.join(outDir, "candid");
177
- if (!import_node_fs2.default.existsSync(candidDir)) {
178
- import_node_fs2.default.mkdirSync(candidDir, { recursive: true });
179
- }
180
- const candidPath = import_node_path.default.join(candidDir, `${canisterName}.did`);
181
- import_node_fs2.default.writeFileSync(candidPath, candidSource);
182
- return candidPath;
105
+ const didTsPath = import_node_path.default.join(declarationsDir, `${canisterName}.d.ts`);
106
+ return import_node_fs.default.existsSync(didTsPath);
183
107
  }
184
108
 
185
109
  // src/templates/reactor.ts
186
110
  var import_node_path2 = __toESM(require("path"), 1);
187
111
  function generateReactorFile(options) {
188
- const {
189
- canisterName,
190
- canisterConfig,
191
- globalClientManagerPath,
192
- hasDeclarations = true,
193
- advanced = false,
194
- didContent
195
- } = options;
196
- const pascalName = toPascalCase(canisterName);
197
- const reactorName = getReactorName(canisterName);
198
- const serviceName = getServiceTypeName(canisterName);
199
- const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
200
- const clientManagerPath = canisterConfig.clientManagerPath ?? globalClientManagerPath ?? "../../lib/client";
201
- const didFileName = import_node_path2.default.basename(canisterConfig.didFile);
202
- const declarationsPath = `./declarations/${didFileName}`;
112
+ const pascalName = toPascalCase(options.canisterName);
113
+ const reactorName = getReactorName(options.canisterName);
114
+ const serviceName = getServiceTypeName(options.canisterName);
115
+ const reactorType = "DisplayReactor";
116
+ const didFileName = import_node_path2.default.basename(options.didFile);
117
+ const baseName = didFileName.replace(/\.did$/, "");
118
+ const declarationsPath = `./declarations/${baseName}`;
119
+ const clientManagerPath = options.clientManagerPath ?? "../../clients";
203
120
  const vars = {
204
- canisterName,
121
+ canisterName: options.canisterName,
205
122
  pascalName,
206
123
  reactorName,
207
124
  serviceName,
208
125
  reactorType,
209
126
  clientManagerPath,
210
- declarationsPath,
211
- useDisplayReactor: canisterConfig.useDisplayReactor !== false
127
+ declarationsPath
212
128
  };
213
- if (!hasDeclarations) {
214
- return generateFallbackReactorFile(vars);
215
- }
216
- if (advanced && didContent) {
217
- return generateAdvancedReactorFile(vars, didContent);
218
- }
219
- return generateSimpleReactorFile(vars);
129
+ return generateStandardReactorFile(vars);
220
130
  }
221
- function reactorInstance(vars) {
131
+ function generateStandardReactorFile(vars) {
222
132
  const {
223
133
  pascalName,
224
134
  reactorName,
225
135
  serviceName,
226
136
  reactorType,
227
- canisterName,
228
- useDisplayReactor
137
+ clientManagerPath,
138
+ declarationsPath,
139
+ canisterName
229
140
  } = vars;
230
- return `/**
231
- * ${pascalName} Reactor \u2014 ${useDisplayReactor ? "Display" : "Candid"} mode.
232
- * ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
141
+ return `import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
142
+ import { clientManager } from "${clientManagerPath}"
143
+ import { idlFactory, type _SERVICE } from "${declarationsPath}"
144
+
145
+ export type ${serviceName} = _SERVICE
146
+
147
+ /**
148
+ * ${pascalName} Display Reactor
233
149
  */
234
150
  export const ${reactorName} = new ${reactorType}<${serviceName}>({
235
151
  clientManager,
236
152
  idlFactory,
237
153
  name: "${canisterName}",
238
- })`;
239
- }
240
- function actorHooks(vars) {
241
- const { pascalName, reactorName } = vars;
242
- return `const {
154
+ })
155
+
156
+ export const {
243
157
  useActorQuery: use${pascalName}Query,
244
158
  useActorSuspenseQuery: use${pascalName}SuspenseQuery,
245
159
  useActorInfiniteQuery: use${pascalName}InfiniteQuery,
@@ -247,297 +161,35 @@ function actorHooks(vars) {
247
161
  useActorMutation: use${pascalName}Mutation,
248
162
  useActorMethod: use${pascalName}Method,
249
163
  } = createActorHooks(${reactorName})
250
-
251
- export {
252
- use${pascalName}Query,
253
- use${pascalName}SuspenseQuery,
254
- use${pascalName}InfiniteQuery,
255
- use${pascalName}SuspenseInfiniteQuery,
256
- use${pascalName}Mutation,
257
- use${pascalName}Method,
258
- }`;
259
- }
260
- function generateSimpleReactorFile(vars) {
261
- const {
262
- pascalName,
263
- reactorType,
264
- clientManagerPath,
265
- declarationsPath,
266
- serviceName
267
- } = vars;
268
- return `/**
269
- * ${pascalName} Reactor
270
- *
271
- * Auto-generated by @ic-reactor/codegen
272
- */
273
-
274
- import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
275
- import { clientManager } from "${clientManagerPath}"
276
- import { idlFactory, type _SERVICE } from "${declarationsPath}"
277
-
278
- export type ${serviceName} = _SERVICE
279
-
280
- ${reactorInstance(vars)}
281
-
282
- ${actorHooks(vars)}
283
-
284
- export { idlFactory }
285
- `;
286
- }
287
- function generateAdvancedReactorFile(vars, didContent) {
288
- const {
289
- pascalName,
290
- reactorName,
291
- serviceName,
292
- reactorType,
293
- clientManagerPath,
294
- declarationsPath
295
- } = vars;
296
- const methods = extractMethods(didContent);
297
- const hasQueryWithoutArgs = methods.some(
298
- (m) => m.type === "query" && !m.hasArgs
299
- );
300
- const hasMutationWithoutArgs = methods.some(
301
- (m) => m.type === "mutation" && !m.hasArgs
302
- );
303
- const extraImports = [];
304
- if (hasQueryWithoutArgs) extraImports.push("createQuery");
305
- if (hasMutationWithoutArgs) extraImports.push("createMutation");
306
- const perMethodHooks = methods.map(({ name, type, hasArgs }) => {
307
- const camelMethod = toCamelCase(name);
308
- if (type === "query") {
309
- if (!hasArgs) {
310
- return `
311
- export const ${camelMethod}Query = createQuery(${reactorName}, {
312
- functionName: "${name}",
313
- })`;
314
- }
315
- return "";
316
- } else {
317
- if (!hasArgs) {
318
- return `
319
- export const ${camelMethod}Mutation = createMutation(${reactorName}, {
320
- functionName: "${name}",
321
- })`;
322
- }
323
- return "";
324
- }
325
- }).filter(Boolean);
326
- return `/**
327
- * ${pascalName} Reactor (Advanced)
328
- *
329
- * Auto-generated by @ic-reactor/codegen
330
- * Includes reactor instance, actor hooks, and per-method static hooks.
331
- */
332
-
333
- import {
334
- ${reactorType},
335
- createActorHooks,${extraImports.length > 0 ? "\n " + extraImports.join(",\n ") + "," : ""}
336
- } from "@ic-reactor/react"
337
- import { clientManager } from "${clientManagerPath}"
338
- import { idlFactory, type _SERVICE } from "${declarationsPath}"
339
-
340
- type ${serviceName} = _SERVICE
341
-
342
- ${reactorInstance(vars)}
343
-
344
- ${actorHooks(vars)}
345
- ${perMethodHooks.length > 0 ? `
346
- // Per-method static hooks (no-args methods only)
347
- ${perMethodHooks.join("\n")}
348
- ` : ""}
349
- export { idlFactory }
350
- export type { ${serviceName} }
351
164
  `;
352
165
  }
353
- function generateFallbackReactorFile(vars) {
354
- const {
355
- canisterName,
356
- pascalName,
357
- serviceName,
358
- reactorType,
359
- clientManagerPath,
360
- declarationsPath
361
- } = vars;
362
- return `/**
363
- * ${pascalName} Reactor
364
- *
365
- * Auto-generated by @ic-reactor/codegen
366
- *
367
- * \u26A0\uFE0F Declarations were not generated. Run:
368
- * npx @icp-sdk/bindgen --input <path-to-did> --output ./${canisterName}/declarations
369
- * Then uncomment the import below and remove the fallback type.
370
- */
371
-
372
- import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
373
- import { clientManager } from "${clientManagerPath}"
374
-
375
- // TODO: Uncomment after generating declarations:
376
- // import { idlFactory, type _SERVICE as ${serviceName} } from "${declarationsPath}"
377
-
378
- // Fallback \u2014 replace with generated types
379
- type ${serviceName} = Record<string, (...args: unknown[]) => Promise<unknown>>
380
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
- const idlFactory = ({ IDL }: { IDL: any }) => IDL.Service({})
382
166
 
383
- ${reactorInstance(vars)}
167
+ // src/templates/client.ts
168
+ function generateClientFile(options = {}) {
169
+ const { queryClientPath } = options;
170
+ return `import { ClientManager } from "@ic-reactor/react"
171
+ ${queryClientPath ? `import { queryClient } from "${queryClientPath}"` : `import { QueryClient } from "@tanstack/react-query"
384
172
 
385
- ${actorHooks(vars)}
173
+ export const queryClient = new QueryClient()`}
386
174
 
387
- export { idlFactory }
388
- export type { ${serviceName} }
389
- `;
390
- }
391
-
392
- // src/templates/query.ts
393
- function generateQueryHook(options) {
394
- const { canisterName, method, type = "query" } = options;
395
- const reactorName = getReactorName(canisterName);
396
- const hookExportName = getHookExportName(method.name, type);
397
- const isSuspense = type === "suspenseQuery";
398
- const creatorFn = isSuspense ? "createSuspenseQuery" : "createQuery";
399
- const factoryFn = isSuspense ? "createSuspenseQueryFactory" : "createQueryFactory";
400
- const hookName = isSuspense ? "useSuspenseQuery" : "useQuery";
401
- if (method.hasArgs) {
402
- return `/**
403
- * Query Factory: ${method.name}
404
- *
175
+ /**
176
+ * IC Reactor Client Manager
177
+ *
405
178
  * Auto-generated by @ic-reactor/codegen
406
- *
407
- * @example
408
- * const { data } = ${hookExportName}([arg1, arg2]).${hookName}()
409
- * const data = await ${hookExportName}([arg1, arg2]).fetch()
410
- * ${hookExportName}([arg1, arg2]).invalidate()
411
179
  */
412
-
413
- import { ${factoryFn} } from "@ic-reactor/react"
414
- import { ${reactorName} } from "../reactor"
415
-
416
- export const ${hookExportName} = ${factoryFn}(${reactorName}, {
417
- functionName: "${method.name}",
180
+ export const clientManager = new ClientManager({
181
+ queryClient,
182
+ withCanisterEnv: true,
418
183
  })
419
- `;
420
- }
421
- return `/**
422
- * Query: ${method.name}
423
- *
424
- * Auto-generated by @ic-reactor/codegen
425
- *
426
- * @example
427
- * const { data } = ${hookExportName}.${hookName}()
428
- * const data = await ${hookExportName}.fetch()
429
- * ${hookExportName}.invalidate()
430
- */
431
-
432
- import { ${creatorFn} } from "@ic-reactor/react"
433
- import { ${reactorName} } from "../reactor"
434
-
435
- export const ${hookExportName} = ${creatorFn}(${reactorName}, {
436
- functionName: "${method.name}",
437
- })
438
- `;
439
- }
440
-
441
- // src/templates/mutation.ts
442
- function generateMutationHook(options) {
443
- const { canisterName, method } = options;
444
- const pascalMethod = toPascalCase(method.name);
445
- const reactorName = getReactorName(canisterName);
446
- const hookExportName = getHookExportName(method.name, "mutation");
447
- return `/**
448
- * Mutation: ${method.name}
449
- *
450
- * Auto-generated by @ic-reactor/codegen
451
- *
452
- * @example
453
- * const { mutate, isPending } = ${hookExportName}.useMutation()
454
- * mutate(${method.hasArgs ? "[arg1, arg2]" : "[]"})
455
- *
456
- * // Direct execution (outside React)
457
- * const result = await ${hookExportName}.execute(${method.hasArgs ? "[arg1, arg2]" : "[]"})
458
- */
459
-
460
- import { createMutation } from "@ic-reactor/react"
461
- import { ${reactorName} } from "../reactor"
462
-
463
- export const ${hookExportName} = createMutation(${reactorName}, {
464
- functionName: "${method.name}",
465
- })
466
-
467
- /** React hook for ${method.name} */
468
- export const use${pascalMethod}Mutation = ${hookExportName}.useMutation
469
-
470
- /** Execute ${method.name} directly (outside React) */
471
- export const execute${pascalMethod} = ${hookExportName}.execute
472
- `;
473
- }
474
-
475
- // src/templates/infiniteQuery.ts
476
- function generateInfiniteQueryHook(options) {
477
- const { canisterName, method, type = "infiniteQuery" } = options;
478
- const reactorName = getReactorName(canisterName);
479
- const serviceName = getServiceTypeName(canisterName);
480
- const hookExportName = getHookExportName(method.name, type);
481
- const reactHookName = getReactHookName(method.name, type);
482
- return `/**
483
- * Infinite Query: ${method.name}
484
- *
485
- * Auto-generated by @ic-reactor/codegen
486
- *
487
- * \u26A0\uFE0F CUSTOMIZATION REQUIRED: Configure getArgs and getNextPageParam below.
488
- *
489
- * @example
490
- * const { data, fetchNextPage, hasNextPage } = ${hookExportName}.useInfiniteQuery()
491
- * const allItems = data?.pages.flatMap(page => page.items) ?? []
492
- */
493
-
494
- import { createInfiniteQuery } from "@ic-reactor/react"
495
- import { ${reactorName}, type ${serviceName} } from "../reactor"
496
-
497
- /** Define your pagination cursor type */
498
- type PageCursor = number
499
-
500
- export const ${hookExportName} = createInfiniteQuery(${reactorName}, {
501
- functionName: "${method.name}",
502
-
503
- initialPageParam: 0 as PageCursor,
504
-
505
- /** Convert page param to method arguments \u2014 customize for your API */
506
- getArgs: (pageParam: PageCursor) => {
507
- return [{ offset: pageParam, limit: 10 }] as Parameters<${serviceName}["${method.name}"]>
508
- },
509
-
510
- /** Extract next page param \u2014 return undefined when no more pages */
511
- getNextPageParam: (lastPage, allPages, lastPageParam) => {
512
- // Example: offset-based
513
- // if (lastPage.items.length < 10) return undefined
514
- // return lastPageParam + 10
515
- return undefined
516
- },
517
- })
518
-
519
- /** React hook for paginated ${method.name} */
520
- export const ${reactHookName} = ${hookExportName}.useInfiniteQuery
521
184
  `;
522
185
  }
523
186
  // Annotate the CommonJS export names for ESM import in node:
524
187
  0 && (module.exports = {
525
188
  declarationsExist,
526
- extractMethods,
527
- formatMethodForDisplay,
189
+ generateClientFile,
528
190
  generateDeclarations,
529
- generateInfiniteQueryHook,
530
- generateMutationHook,
531
- generateQueryHook,
532
191
  generateReactorFile,
533
- getHookExportName,
534
- getHookFileName,
535
- getMethodsByType,
536
- getReactHookName,
537
192
  getReactorName,
538
193
  getServiceTypeName,
539
- parseDIDFile,
540
- saveCandidFile,
541
- toCamelCase,
542
194
  toPascalCase
543
195
  });