@ic-reactor/codegen 0.12.1 → 0.13.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.
@@ -14,6 +14,7 @@ import { didToJs, didToTs } from "@ic-reactor/parser"
14
14
  import path from "node:path"
15
15
  import fs from "node:fs"
16
16
  import type { GeneratorResult } from "../types.js"
17
+ import { CodegenConfigError, resolveDeclarationsBaseName } from "../validate.js"
17
18
 
18
19
  export interface DeclarationsGeneratorOptions {
19
20
  /** Absolute path to the .did file */
@@ -31,18 +32,95 @@ export interface DeclarationsGeneratorResult {
31
32
  error?: string
32
33
  }
33
34
 
35
+ /**
36
+ * Move `from` onto `to`, replacing whatever is already there.
37
+ *
38
+ * `rename(2)` refuses to replace a non-empty directory, so the previous output
39
+ * is moved aside first and only removed once the new directory is in place. If
40
+ * the second rename fails the old directory is put back, so the observable
41
+ * states are "old output" and "new output" — never "no output".
42
+ */
43
+ function replaceDirectory(from: string, to: string): void {
44
+ if (!fs.existsSync(to)) {
45
+ fs.renameSync(from, to)
46
+ return
47
+ }
48
+
49
+ // Same parent as `to`, so this is a rename within one filesystem. Dot-prefixed
50
+ // because a crash between the two renames below strands this directory next
51
+ // to the real one: `declarations.old-XYZ` sits inside the project's source
52
+ // tree and TypeScript would compile it, producing duplicate declarations on
53
+ // top of whatever the crash already caused.
54
+ const displaced = fs.mkdtempSync(
55
+ path.join(path.dirname(to), `.${path.basename(to)}.old-`)
56
+ )
57
+ // mkdtemp created the directory; rename needs the name to be free.
58
+ fs.rmdirSync(displaced)
59
+ fs.renameSync(to, displaced)
60
+
61
+ try {
62
+ fs.renameSync(from, to)
63
+ } catch (error) {
64
+ fs.renameSync(displaced, to)
65
+ throw error
66
+ }
67
+
68
+ fs.rmSync(displaced, { recursive: true, force: true })
69
+ }
70
+
34
71
  /**
35
72
  * Generate TypeScript declarations from a Candid file.
36
73
  *
37
- * Always cleans and regenerates the declarations directory to ensure
38
- * it's in sync with the source .did file.
74
+ * Generation happens in a staging directory that is swapped over the existing
75
+ * `declarations/` only after every file has been written. The parser runs on
76
+ * user-authored Candid and throws on a syntax error — under the vite plugin
77
+ * that is a *normal* watch-mode event, one keystroke in a .did file — so
78
+ * deleting the previous output before parsing turned every typo into a broken
79
+ * build with no declarations at all. A failure now leaves the previous
80
+ * declarations byte-identical.
81
+ */
82
+ /**
83
+ * The export the generated reactor imports. Its presence in `didToJs` output is
84
+ * the authoritative "this .did describes a service" test — see the note in
85
+ * generateDeclarations.
86
+ */
87
+ const HAS_IDL_FACTORY = /\bexport\s+const\s+idlFactory\b/
88
+
89
+ /**
90
+ * Marker naming the canister an output directory belongs to. Dot-prefixed so
91
+ * it stays out of the generated surface consumers import.
39
92
  */
93
+ export const OWNER_FILE = ".ic-reactor-owner"
94
+
40
95
  export async function generateDeclarations(
41
96
  options: DeclarationsGeneratorOptions
42
97
  ): Promise<DeclarationsGeneratorResult> {
43
98
  const { didFile, outDir, canisterName } = options
44
99
 
45
- if (!fs.existsSync(didFile)) {
100
+ const declarationsDir = path.join(outDir, "declarations")
101
+
102
+ // Checked before the file itself: an absolute `didFile` ending in ".." is not
103
+ // normalized by the caller and does exist, so without this the diagnostic
104
+ // would be about the file type rather than about the path being a directory.
105
+ let baseName: string
106
+ try {
107
+ baseName = resolveDeclarationsBaseName(didFile)
108
+ } catch (error) {
109
+ if (error instanceof CodegenConfigError) {
110
+ return {
111
+ success: false,
112
+ declarationsDir: "",
113
+ files: [],
114
+ error: `[${canisterName}] ${error.message}`,
115
+ }
116
+ }
117
+ throw error
118
+ }
119
+
120
+ let didStat: fs.Stats
121
+ try {
122
+ didStat = fs.statSync(didFile)
123
+ } catch {
46
124
  return {
47
125
  success: false,
48
126
  declarationsDir: "",
@@ -51,34 +129,72 @@ export async function generateDeclarations(
51
129
  }
52
130
  }
53
131
 
54
- const declarationsDir = path.join(outDir, "declarations")
55
- const baseName = path.basename(didFile, ".did") // e.g. "backend" from "backend.did"
132
+ // A fifo or character device reads forever. Refusing anything that is not a
133
+ // regular file turns an indefinite hang into a diagnosable error.
134
+ if (!didStat.isFile()) {
135
+ return {
136
+ success: false,
137
+ declarationsDir: "",
138
+ files: [],
139
+ error: `[${canisterName}] DID path is not a regular file: ${didFile}`,
140
+ }
141
+ }
142
+
143
+ let staging: string | undefined
56
144
 
57
145
  try {
58
146
  // Read the DID content before any directory manipulation
59
147
  const didContent = fs.readFileSync(didFile, "utf-8")
60
148
 
61
- // Ensure output dir exists
62
- if (!fs.existsSync(outDir)) {
63
- fs.mkdirSync(outDir, { recursive: true })
64
- }
149
+ const jsContent = didToJs(didContent)
150
+ const tsContent = didToTs(didContent)
65
151
 
66
- // Clean and recreate declarations dir for a fresh generation
67
- if (fs.existsSync(declarationsDir)) {
68
- fs.rmSync(declarationsDir, { recursive: true, force: true })
152
+ // A .did that parses but declares no service is a normal thing to have — a
153
+ // shared types file, say — but it compiles to a module with no `idlFactory`
154
+ // and no `_SERVICE`, which is exactly what the generated reactor imports.
155
+ // Caught here the caller gets the .did path; not caught, the consumer's
156
+ // build fails on a file we just reported as generated.
157
+ //
158
+ // The test is on the GENERATED OUTPUT, not on `parseDid(...).service`. That
159
+ // AST field is only populated for an inline body; it is null for every
160
+ // alias form — `service : S;`, `service : (args) -> S;`, `service name : S;`
161
+ // — which the parser nonetheless compiles to a complete idlFactory. Gating
162
+ // on the AST field rejected those valid files outright.
163
+ if (!HAS_IDL_FACTORY.test(jsContent)) {
164
+ return {
165
+ success: false,
166
+ declarationsDir,
167
+ files: [],
168
+ error:
169
+ `[${canisterName}] ${didFile} produces no idlFactory, so there is no service ` +
170
+ `to generate against. Point this canister at the .did file that declares its ` +
171
+ `service.`,
172
+ }
69
173
  }
70
- fs.mkdirSync(declarationsDir, { recursive: true })
71
174
 
72
- const jsContent = didToJs(didContent)
73
- const tsContent = didToTs(didContent)
175
+ // Ensure output dir exists — it is also the staging directory's parent, so
176
+ // the swap below stays a same-filesystem rename.
177
+ fs.mkdirSync(outDir, { recursive: true })
178
+
179
+ // Record which canister owns this directory. The pipeline reads it to stop
180
+ // a second canister generating into the same outDir and wiping the first
181
+ // one's declarations. It lives here rather than in index.generated.ts
182
+ // because `--bindgen-only` never writes that file, and the declarations
183
+ // replacement it skips past is exactly the destructive step.
184
+ fs.writeFileSync(path.join(outDir, OWNER_FILE), `${canisterName}\n`)
185
+
186
+ staging = fs.mkdtempSync(path.join(outDir, ".declarations.tmp-"))
74
187
 
75
188
  const jsPath = path.join(declarationsDir, `${baseName}.js`)
76
189
  const dtsPath = path.join(declarationsDir, `${baseName}.d.ts`)
77
190
  const didCopyPath = path.join(declarationsDir, `${baseName}.did`)
78
191
 
79
- fs.writeFileSync(jsPath, jsContent)
80
- fs.writeFileSync(dtsPath, tsContent)
81
- fs.writeFileSync(didCopyPath, didContent)
192
+ fs.writeFileSync(path.join(staging, `${baseName}.js`), jsContent)
193
+ fs.writeFileSync(path.join(staging, `${baseName}.d.ts`), tsContent)
194
+ fs.writeFileSync(path.join(staging, `${baseName}.did`), didContent)
195
+
196
+ replaceDirectory(staging, declarationsDir)
197
+ staging = undefined
82
198
 
83
199
  return {
84
200
  success: true,
@@ -97,16 +213,43 @@ export async function generateDeclarations(
97
213
  files: [],
98
214
  error: `[${canisterName}] Failed to generate declarations: ${message}`,
99
215
  }
216
+ } finally {
217
+ // Reached on the failure path and on an interrupted swap; a completed swap
218
+ // has already cleared `staging`.
219
+ if (staging) fs.rmSync(staging, { recursive: true, force: true })
100
220
  }
101
221
  }
102
222
 
103
223
  /**
104
224
  * Check if declarations already exist for a canister.
225
+ *
226
+ * Declarations are written under the *.did basename*, which need not equal the
227
+ * canister name — `{ name: "backend", didFile: "service.did" }` writes
228
+ * `declarations/service.d.ts`. Pass `didFile` for an exact answer; without it
229
+ * this falls back to any `.d.ts` in the directory, because a bare
230
+ * `<canisterName>.d.ts` check reports "missing" for perfectly good output.
105
231
  */
106
232
  export function declarationsExist(
107
233
  outDir: string,
108
- canisterName: string
234
+ canisterName: string,
235
+ didFile?: string
109
236
  ): boolean {
110
- const dtsPath = path.join(outDir, "declarations", `${canisterName}.d.ts`)
111
- return fs.existsSync(dtsPath)
237
+ const declarationsDir = path.join(outDir, "declarations")
238
+
239
+ if (didFile !== undefined) {
240
+ const baseName = path.basename(didFile, ".did")
241
+ return fs.existsSync(path.join(declarationsDir, `${baseName}.d.ts`))
242
+ }
243
+
244
+ if (fs.existsSync(path.join(declarationsDir, `${canisterName}.d.ts`))) {
245
+ return true
246
+ }
247
+
248
+ try {
249
+ return fs
250
+ .readdirSync(declarationsDir)
251
+ .some((entry) => entry.endsWith(".d.ts"))
252
+ } catch {
253
+ return false
254
+ }
112
255
  }
@@ -20,9 +20,9 @@
20
20
  * } = createActorHooks(backendReactor)
21
21
  */
22
22
 
23
- import path from "node:path"
24
23
  import { toPascalCase, getReactorName, getServiceTypeName } from "../naming.js"
25
24
  import type { CodegenTarget, ReactorClassName } from "../types.js"
25
+ import { resolveDeclarationsBaseName } from "../validate.js"
26
26
 
27
27
  export interface ReactorGeneratorOptions {
28
28
  /** Canister name (e.g. "backend") */
@@ -88,8 +88,10 @@ export function generateReactorFile(options: ReactorGeneratorOptions): string {
88
88
  const reactorName = getReactorName(canisterName)
89
89
  const serviceName = getServiceTypeName(canisterName)
90
90
 
91
- // Derive the declarations import path from the .did filename
92
- const baseName = path.basename(didFile, ".did")
91
+ // Derive the declarations import path from the .did filename. The helper
92
+ // rejects stems like "." and ".." rather than emitting an import of a
93
+ // directory — this runs even for callers that bypass the pipeline.
94
+ const baseName = resolveDeclarationsBaseName(didFile)
93
95
  const declarationsPath = `./declarations/${baseName}`
94
96
  const reactorImportSource = getReactorClassImportSource(
95
97
  reactorClass,
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ export {
30
30
  assertContainedPath,
31
31
  assertOneOf,
32
32
  resolveContainedOutDir,
33
+ resolveDeclarationsBaseName,
33
34
  CodegenConfigError,
34
35
  CANISTER_NAME_PATTERN,
35
36
  REACTOR_CLASS_NAMES,
@@ -40,8 +41,5 @@ export type {
40
41
  ValidateCanisterConfigOptions,
41
42
  } from "./validate.js"
42
43
 
43
- export { parseDIDFile, extractMethods } from "./parser.js"
44
- export type { MethodInfo, MethodType } from "./parser.js"
45
-
46
44
  // Individual Generators (Advanced Usage)
47
45
  export * from "./generators/index.js"
package/src/pipeline.ts CHANGED
@@ -20,12 +20,17 @@ import type {
20
20
  GeneratorResult,
21
21
  ReactorClassName,
22
22
  } from "./types.js"
23
- import { generateDeclarations } from "./generators/declarations.js"
23
+ import { generateDeclarations, OWNER_FILE } from "./generators/declarations.js"
24
24
  import {
25
25
  generateReactorEntryFile,
26
26
  generateReactorFile,
27
27
  } from "./generators/reactor.js"
28
- import { assertSafeCanisterConfig, CodegenConfigError } from "./validate.js"
28
+ import {
29
+ assertSafeCanisterConfig,
30
+ CodegenConfigError,
31
+ resolveDeclarationsBaseName,
32
+ } from "./validate.js"
33
+ import { getReactorName, getServiceTypeName } from "./naming.js"
29
34
 
30
35
  export interface PipelineOptions {
31
36
  /** Canister name and config */
@@ -61,13 +66,114 @@ function normalizeFileContent(content: string): string {
61
66
  return content.replace(/\r\n/g, "\n").trim()
62
67
  }
63
68
 
64
- function isLegacyGeneratedIndexFile(content: string): boolean {
65
- return (
66
- content.includes("Auto-generated by @ic-reactor/codegen") &&
67
- content.includes("createActorHooks(")
69
+ /** Header line every version of this generator has written into its output. */
70
+ const GENERATED_MARKER = "Auto-generated by @ic-reactor/codegen"
71
+
72
+ /**
73
+ * The reactor's `name:` property as this generator emits it. Canister names are
74
+ * restricted to `CANISTER_NAME_PATTERN`, so the emitted line is exactly this.
75
+ */
76
+ const GENERATED_CANISTER_NAME = /^ {2}name: "([A-Za-z0-9_.-]+)",$/m
77
+
78
+ /** Every `export …` statement, one per line, as the generator writes them. */
79
+ const EXPORT_STATEMENT = /^export\b.*$/gm
80
+
81
+ /**
82
+ * Decide whether an existing `index.ts` is still the pre-split generated
83
+ * reactor file, which this pipeline migrates to the thin wrapper.
84
+ *
85
+ * The old test — the header marker plus the substring `createActorHooks(` —
86
+ * matched any file that had *ever* been generated, including one the user has
87
+ * since filled with their own factories and hooks. Migration would overwrite it
88
+ * with six lines of boilerplate and report the write as an ordinary success.
89
+ *
90
+ * So the shape is checked, not just the header: the file must still declare the
91
+ * reactor and hooks for *this* canister and must export nothing the old
92
+ * generator did not write. Anything the user added — another `export const`, an
93
+ * `export * from`, a re-export — takes the file out of scope and it is
94
+ * preserved. Edits that add no export are still possible, which is why the
95
+ * caller keeps a backup before writing.
96
+ */
97
+ function isLegacyGeneratedIndexFile(
98
+ content: string,
99
+ canisterName: string
100
+ ): boolean {
101
+ if (!content.includes(GENERATED_MARKER)) return false
102
+
103
+ const reactorName = getReactorName(canisterName)
104
+ const serviceName = getServiceTypeName(canisterName)
105
+
106
+ // The two lines that make this the generated reactor for this canister.
107
+ if (!content.includes(`export const ${reactorName} = new `)) return false
108
+ if (!content.includes(`= createActorHooks(${reactorName})`)) return false
109
+
110
+ const allowed = [
111
+ `export type ${serviceName} = _SERVICE`,
112
+ `export const ${reactorName} = new `,
113
+ // Opens the destructured hook block; its members are not export statements.
114
+ "export const {",
115
+ ]
116
+
117
+ const statements = normalizeFileContent(content).match(EXPORT_STATEMENT) ?? []
118
+
119
+ return statements.every((statement) =>
120
+ allowed.some((prefix) => statement.startsWith(prefix))
68
121
  )
69
122
  }
70
123
 
124
+ /**
125
+ * Copy `filePath` next to itself before it is overwritten, and return the
126
+ * backup path.
127
+ *
128
+ * Migration decides from the file's contents that it is safe to replace. That
129
+ * inference can be wrong for an edit that adds no export, and the file being
130
+ * replaced may be the only copy of work the user has not committed, so the
131
+ * original is kept rather than deleted. An existing backup is never clobbered.
132
+ */
133
+ function backUpFile(filePath: string): string {
134
+ let backupPath = `${filePath}.bak`
135
+ for (let n = 2; fs.existsSync(backupPath); n += 1) {
136
+ backupPath = `${filePath}.bak.${n}`
137
+ }
138
+ fs.copyFileSync(filePath, backupPath)
139
+ return backupPath
140
+ }
141
+
142
+ /**
143
+ * Read the canister name out of an existing `index.generated.ts`, if the file
144
+ * exists and this package wrote it.
145
+ *
146
+ * Two canisters pointed at the same `outDir` overwrite each other's
147
+ * `index.generated.ts` and — since the declarations directory is replaced
148
+ * wholesale — each other's declarations too, with both runs reporting success.
149
+ * The generated file records which canister owns the directory, so the second
150
+ * canister can be stopped before it destroys the first one's output.
151
+ */
152
+ function findOutDirOwner(outDir: string): string | undefined {
153
+ // Written by generateDeclarations, so it is present for `--bindgen-only`
154
+ // runs too — those skip index.generated.ts but still replace declarations/,
155
+ // which is the step that destroys the other canister's output.
156
+ try {
157
+ const owner = fs.readFileSync(path.join(outDir, OWNER_FILE), "utf-8").trim()
158
+ if (owner) return owner
159
+ } catch {
160
+ // fall through to the legacy marker
161
+ }
162
+
163
+ // Directories generated before the marker existed only carry the canister
164
+ // name inside index.generated.ts.
165
+ let content: string
166
+ try {
167
+ content = fs.readFileSync(path.join(outDir, "index.generated.ts"), "utf-8")
168
+ } catch {
169
+ return undefined
170
+ }
171
+
172
+ if (!content.includes(GENERATED_MARKER)) return undefined
173
+
174
+ return GENERATED_CANISTER_NAME.exec(content)?.[1]
175
+ }
176
+
71
177
  function isManagedEntryWrapper(
72
178
  content: string,
73
179
  expectedEntryContent: string
@@ -149,10 +255,45 @@ export async function runCanisterPipeline(
149
255
  }
150
256
  }
151
257
 
258
+ // The .did basename names the generated declaration files and is the tail of
259
+ // the import specifier the reactor emits, so it is validated here, before any
260
+ // filesystem work, rather than surfacing as a broken import later.
261
+ try {
262
+ resolveDeclarationsBaseName(resolvedDidFile)
263
+ } catch (err) {
264
+ if (err instanceof CodegenConfigError) {
265
+ return {
266
+ canisterName: name,
267
+ success: false,
268
+ files,
269
+ error: `[${name}] ${err.message}`,
270
+ }
271
+ }
272
+ throw err
273
+ }
274
+
152
275
  // Per-canister outDir overrides global outDir; both are resolved and
153
276
  // containment-checked by assertSafeCanisterConfig above.
154
277
  const canisterOutDir = validated.outDir
155
278
 
279
+ // Refuse to generate into a directory another canister already owns. This has
280
+ // to happen before the declarations step, which replaces `declarations/`
281
+ // wholesale and would otherwise take the other canister's output with it.
282
+ const owner = findOutDirOwner(canisterOutDir)
283
+ if (owner !== undefined && owner !== name) {
284
+ return {
285
+ canisterName: name,
286
+ success: false,
287
+ files,
288
+ error:
289
+ `[${name}] Output directory ${canisterOutDir} was generated for canister ` +
290
+ `"${owner}". Two canisters cannot share an outDir — each run replaces the ` +
291
+ `declarations directory and index.generated.ts, so they would overwrite each ` +
292
+ `other. Give each canister its own "outDir". If you renamed "${owner}" to ` +
293
+ `"${name}", delete that directory and regenerate.`,
294
+ }
295
+ }
296
+
156
297
  // clientManagerPath falls back to global, then a safe default
157
298
  const resolvedClientManagerPath = validated.clientManagerPath
158
299
 
@@ -220,11 +361,15 @@ export async function runCanisterPipeline(
220
361
  } else {
221
362
  const existingEntryContent = fs.readFileSync(entryPath, "utf-8")
222
363
 
223
- if (
224
- isLegacyGeneratedIndexFile(existingEntryContent) ||
225
- isManagedEntryWrapper(existingEntryContent, entryContent)
226
- ) {
364
+ if (isManagedEntryWrapper(existingEntryContent, entryContent)) {
365
+ fs.writeFileSync(entryPath, entryContent)
366
+ files.push({ success: true, filePath: entryPath })
367
+ } else if (isLegacyGeneratedIndexFile(existingEntryContent, name)) {
368
+ // Migration replaces a file the user owns, on the strength of a
369
+ // content match. Keep the original so a wrong match is recoverable.
370
+ const backupPath = backUpFile(entryPath)
227
371
  fs.writeFileSync(entryPath, entryContent)
372
+ files.push({ success: true, filePath: backupPath })
228
373
  files.push({ success: true, filePath: entryPath })
229
374
  } else {
230
375
  files.push({ success: true, filePath: entryPath, skipped: true })
package/src/validate.ts CHANGED
@@ -167,6 +167,44 @@ export function assertSafeModuleSpecifier(
167
167
  }
168
168
  }
169
169
 
170
+ /**
171
+ * Derive the declarations file stem from a `.did` path and assert it is usable
172
+ * both as a file name and as the tail of the import specifier we emit.
173
+ *
174
+ * `path.basename` is happy to hand back `"."` or `".."`: a `didFile` of
175
+ * `"canisters/.."` makes the generated reactor `import … from
176
+ * "./declarations/.."`, a *directory* reference — reported as a successful
177
+ * generation, rejected by the consumer's bundler. Validating the derived
178
+ * specifier here is the same treatment `clientManagerPath` gets, for the same
179
+ * reason: it is config-supplied text that ends up inside an `import`.
180
+ */
181
+ export function resolveDeclarationsBaseName(didFile: unknown): string {
182
+ if (typeof didFile !== "string" || didFile.length === 0) {
183
+ throw new CodegenConfigError(
184
+ `Invalid didFile: expected a non-empty string, received ${
185
+ didFile === undefined ? "undefined" : JSON.stringify(didFile)
186
+ }.`
187
+ )
188
+ }
189
+
190
+ const baseName = path.basename(didFile, ".did")
191
+
192
+ if (baseName === "" || baseName === "." || baseName === "..") {
193
+ throw new CodegenConfigError(
194
+ `Invalid didFile ${JSON.stringify(didFile)}: its file name ${JSON.stringify(baseName)} ` +
195
+ `refers to a directory, not a Candid file. Generated declarations are named after the ` +
196
+ `.did file, so this would emit an import of a directory.`
197
+ )
198
+ }
199
+
200
+ assertSafeModuleSpecifier(
201
+ `declarations import derived from didFile ${JSON.stringify(didFile)}`,
202
+ `./declarations/${baseName}`
203
+ )
204
+
205
+ return baseName
206
+ }
207
+
170
208
  /** Reactor classes the generator knows how to emit an import for. */
171
209
  export const REACTOR_CLASS_NAMES: readonly ReactorClassName[] = [
172
210
  "Reactor",
@@ -1,18 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`Bindgen > generateDeclarations creates correct files > js-declarations 1`] = `
4
- "export const idlFactory = ({ IDL }) => {
5
- return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], ['query']) });
6
- };
7
- export const init = ({ IDL }) => { return []; };"
8
- `;
9
-
10
- exports[`Bindgen > generateDeclarations creates correct files > ts-declarations 1`] = `
11
- "import type { Principal } from '@icp-sdk/core/principal';
12
- import type { ActorMethod } from '@icp-sdk/core/agent';
13
- import type { IDL } from '@icp-sdk/core/candid';
14
-
15
- export interface _SERVICE { 'greet' : ActorMethod<[string], string> }
16
- export declare const idlFactory: IDL.InterfaceFactory;
17
- export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];"
18
- `;
@@ -1,127 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
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"
6
- import { clientManager } from "../../clients"
7
- import { idlFactory, type _SERVICE } from "./declarations/backend"
8
-
9
- export type BackendService = _SERVICE
10
-
11
- /**
12
- * Backend Reactor
13
- *
14
- * Auto-generated by @ic-reactor/codegen — do not edit.
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.
19
- */
20
- export const backendReactor = new DisplayReactor<BackendService>({
21
- clientManager,
22
- idlFactory,
23
- name: "backend",
24
- })
25
-
26
- export const {
27
- useActorQuery: useBackendQuery,
28
- useActorSuspenseQuery: useBackendSuspenseQuery,
29
- useActorInfiniteQuery: useBackendInfiniteQuery,
30
- useActorSuspenseInfiniteQuery: useBackendSuspenseInfiniteQuery,
31
- useActorMutation: useBackendMutation,
32
- useActorMethod: useBackendMethod,
33
- } = createActorHooks(backendReactor)
34
- "
35
- `;
36
-
37
- exports[`Reactor generator > supports Reactor mode generation > reactor-index 1`] = `
38
- "import { createActorHooks } from "@ic-reactor/react"
39
- import { Reactor } from "@ic-reactor/react"
40
- import { clientManager } from "../../clients"
41
- import { idlFactory, type _SERVICE } from "./declarations/workflow_engine"
42
-
43
- export type WorkflowEngineService = _SERVICE
44
-
45
- /**
46
- * WorkflowEngine Reactor
47
- *
48
- * Auto-generated by @ic-reactor/codegen — do not edit.
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.
53
- */
54
- export const workflowEngineReactor = new Reactor<WorkflowEngineService>({
55
- clientManager,
56
- idlFactory,
57
- name: "workflow_engine",
58
- })
59
-
60
- export const {
61
- useActorQuery: useWorkflowEngineQuery,
62
- useActorSuspenseQuery: useWorkflowEngineSuspenseQuery,
63
- useActorInfiniteQuery: useWorkflowEngineInfiniteQuery,
64
- useActorSuspenseInfiniteQuery: useWorkflowEngineSuspenseInfiniteQuery,
65
- useActorMutation: useWorkflowEngineMutation,
66
- useActorMethod: useWorkflowEngineMethod,
67
- } = createActorHooks(workflowEngineReactor)
68
- "
69
- `;
70
-
71
- exports[`Reactor generator > supports candid reactor subclasses > metadata-display-reactor-index 1`] = `
72
- "import { createActorHooks } from "@ic-reactor/react"
73
- import { MetadataDisplayReactor } from "@ic-reactor/candid"
74
- import { clientManager } from "../../clients"
75
- import { idlFactory, type _SERVICE } from "./declarations/ledger"
76
-
77
- export type LedgerService = _SERVICE
78
-
79
- /**
80
- * Ledger Reactor
81
- *
82
- * Auto-generated by @ic-reactor/codegen — do not edit.
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.
87
- */
88
- export const ledgerReactor = new MetadataDisplayReactor<LedgerService>({
89
- clientManager,
90
- idlFactory,
91
- name: "ledger",
92
- })
93
-
94
- export const {
95
- useActorQuery: useLedgerQuery,
96
- useActorSuspenseQuery: useLedgerSuspenseQuery,
97
- useActorInfiniteQuery: useLedgerInfiniteQuery,
98
- useActorSuspenseInfiniteQuery: useLedgerSuspenseInfiniteQuery,
99
- useActorMutation: useLedgerMutation,
100
- useActorMethod: useLedgerMethod,
101
- } = createActorHooks(ledgerReactor)
102
- "
103
- `;
104
-
105
- exports[`Reactor generator > supports core target generation without React hooks > core-display-reactor-index 1`] = `
106
- "import { DisplayReactor } from "@ic-reactor/core"
107
- import { clientManager } from "../../clients"
108
- import { idlFactory, type _SERVICE } from "./declarations/backend"
109
-
110
- export type BackendService = _SERVICE
111
-
112
- /**
113
- * Backend Reactor
114
- *
115
- * Auto-generated by @ic-reactor/codegen — do not edit.
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.
120
- */
121
- export const backendReactor = new DisplayReactor<BackendService>({
122
- clientManager,
123
- idlFactory,
124
- name: "backend",
125
- })
126
- "
127
- `;