Skip to content
IC Reactor

API Reference

The package exports didToJs, didToTs, parseDid, validateIDL, verifyCompatibility, the deprecated verifyCompatability, and a default WASM initializer, init, with a synchronous initSync companion.

didToJs(candid: string): string

Compiles a Candid interface string into a JavaScript module string.

Parameters:

  • candid: The Candid interface definition string.

Returns:

  • A string containing the JavaScript module source.

Output Exports: The generated JavaScript module exports:

  • idlFactory: An IDL.InterfaceFactory function.
  • init: A function that returns initialization arguments.
import { didToJs } from "@ic-reactor/parser"
const js = didToJs("service:{}")
// export const idlFactory ...

didToTs(candid: string): string

Compiles a Candid interface string into a TypeScript declaration string.

Parameters:

  • candid: The Candid interface definition string.

Returns:

  • A string containing the TypeScript declaration source.

Output Includes:

  • Imports from @icp-sdk/core (or legacy @dfinity/agent).
  • _SERVICE interface defining the actor type.
  • idlFactory and init type declarations.
import { didToTs } from "@ic-reactor/parser"
const ts = didToTs("service:{}")
// export interface _SERVICE ...

didToTs exports each Candid type under its own name, next to the _SERVICE interface. A type TypeScript cannot declare or refer to under its name is renamed with _ appended, and didToJs uses the same name:

  • a type named like a TypeScript type keyword: string, number, bigint, symbol, object, any, unknown, never or undefined;
  • a type named like a TypeScript type operator: keyof, readonly, unique or infer;
  • a type named _SERVICE that is not the service itself;
  • a type named like a JavaScript keyword, such as class (candid_parser’s own rule).
import { didToTs } from "@ic-reactor/parser"
didToTs(`type string = record { value : text };
service : { get : () -> (string) query }`)
// export interface string_ { 'value' : string }
// export interface _SERVICE { 'get' : ActorMethod<[], string_> }

When string_ is taken by another type, the next free name (string__) is used. A service type named _SERVICE that the service uses (service : _SERVICE) keeps its name and is declared once, as the _SERVICE interface.

Candid identifies a named record field or variant tag by the hash of its name. @icp-sdk/core reads an IDL key spelled _<digits>_ or _0x<hex digits>_, for a number below 2^32, as that number instead, the spelling it uses for numeric fields such as 0 : nat. So didToJs and didToTs print a field named like that under its hash, and it is sent and read by that hash:

import { didToTs } from "@ic-reactor/parser"
didToTs(`service : { get : () -> (record { _0_ : nat; 0 : text }) query }`)
// … ActorMethod<[], { _0_ : string, _4735054_ : bigint }>

Use the printed key (_4735054_) for the value in calls and results. parseDid names the field by the same key. Names that do not match the pattern print as written. A hand-written .did that spells a Motoko numeric field as _0_ must write it as 0, the way Motoko prints it; spelled _0_, it is the field named _0_.

parseDid(candid: string): CandidSchema

Parses a Candid interface string into a structured schema object (type declarations plus the service declaration), including any metadata tags.

Returns:

  • A CandidSchema: { types: CandidTypeDeclaration[]; service: CandidServiceDeclaration | null }. Doc-comment metadata and validation tags are carried on the nodes — see Candid Metadata Tags.
  • Each record field and variant tag is named by the key didToJs prints for it: _0_ for the numeric field 0, and _4735054_ for a field named _0_ (see Field names that look like numeric ids). Types keep their Candid names, including one didToTs renames.
import { parseDid } from "@ic-reactor/parser"
const schema = parseDid("service:{}")
// { types: [...], service: { ... } | null }

validateIDL(candid: string): boolean

Returns true when the Candid source parses and type-checks. It never returns false: for invalid Candid it throws the parser’s message as a string (a syntax error, or a type error such as Unbound type identifier T), so call it inside try / catch. CandidAdapter.validateCandid(), which it backs, catches that and returns false.

import { validateIDL } from "@ic-reactor/parser"
try {
validateIDL("service : { greet : (text) -> (text) query }") // true
} catch (message) {
console.error(message) // e.g. "Unbound type identifier T"
}

verifyCompatibility(oldDid: string, newDid: string): boolean

Returns true when newDid is a compatible upgrade of oldDid, so every client of the old interface can keep calling the new one, and false when it is not. Use it to guard canister upgrades against breaking interface changes.

Throws when either source does not parse or type-check, or declares no service.

import { verifyCompatibility } from "@ic-reactor/parser"
const oldDid = "service : { greet : (text) -> (text) query }"
// Adding a method keeps every client of the old interface working
const newDid = `service : {
greet : (text) -> (text) query;
farewell : (text) -> (text) query;
}`
if (!verifyCompatibility(oldDid, newDid)) {
throw new Error("The new interface breaks existing clients")
}

verifyCompatability(a: string, b: string): boolean

Use verifyCompatibility(oldDid, newDid) instead. This older export returns true when a is a compatible upgrade of b, so it takes the new interface first, and it throws instead of returning false when the upgrade is not compatible.

init(module_or_path?): Promise<InitOutput>

Instantiates the WASM module. Required once before any other function on the web build. A synchronous initSync(module) variant is also exported for environments where the WASM bytes are already available.

On Node both exist for ES module imports and do nothing, because the Node build instantiates the module when it loads. require returns only the parser functions.

import init, { initSync } from "@ic-reactor/parser"
await init()

Invalid Candid makes a function throw the parser’s message as a string, such as Candid parser error: Unrecognized EOF found at 11.

Some inputs make the parser itself fail, through a Rust panic or input nested or repeated deeply enough to exhaust the stack. The function then throws an Error whose message says the input could not be parsed, with the original WebAssembly.RuntimeError or RangeError as its cause. Before it throws, the parser replaces its WebAssembly instance with a new one built from the module it already compiled, so later calls work normally and nothing is fetched again.

The web and Node builds behave this way. dist/bundler, which no export condition selects, keeps wasm-bindgen’s generated loader and does not restart.

The package builds for multiple targets to ensure compatibility across environments.

Folder Format Resolved by
dist/web ES Modules browser, workerd and default conditions
dist/nodejs ES Modules (index.mjs), CommonJS (index.js) node condition, for import and require
dist/bundler ES Modules Built and shipped, but not reachable via exports

There is no ./web, ./nodejs or ./bundler subpath — the package has a single . entry and the right build is picked by export conditions. Bundlers resolve the browser condition to dist/web, which requires await init().