Packages and crates

Every publishable and internal unit in the repository, what it is for, and how to depend on it.

This repository ships one published Rust crate, two published npm packages, and three more units that exist for good reasons but are deliberately on no registry at all. This page is the whole inventory: what each one is, who it is for, where its manifest lives, whether you can install it today, and the exact line you write to depend on it.

The short version. If you write Rust, you want candid-core. If you write TypeScript, you want @candid-core/schema. Everything else on this page either produces one of those two, or tests them.

The pipeline in one view#

Every unit sits somewhere on one path. Candid source text goes in at the left; a canonical, validated document comes out of the middle; typed language bindings come out of the right. A Contract is this project's name for that middle document. It holds an arena of numbered type nodes, a list of named declarations pointing into it, an optional actor, and the content-addressed identities computed over it.

01

.did source#

Your Candid interface and every file it imports. Text, with whitespace, comments and declaration spellings that mean nothing to the wire.

02

Compilation#

candid-core hands the source to the exact-pinned candid_parser for parsing and type checking, then projects the result into a validated graph under an explicit resource budget.

03

Contract document#

Canonical JSON: types, declarations, an optional actor, and identities. Optionally wrapped in a ContractEnvelope that also carries the field-name table.

04

Bindings and runtime#

candid-core-ts turns the graph into a TypeScript module. @candid-core/schema validates values against it, encodes and decodes Candid bytes, and calls canisters.

Steps 02 and 03 are Rust. Step 04 is split. The generator is Rust; the runtime it targets is TypeScript. @candid-core/cli, published on npm, lets a JavaScript-only project get from 01 to 04 without a Rust toolchain. It is the same Rust code compiled to WebAssembly, not a reimplementation.

The full inventory#

UnitKindManifestRegistryVersion
candid-coreRust libraryCargo.tomlcrates.io0.1.0-beta.3
candid-core (binary)CLI, same crateCargo.toml [[bin]]crates.io0.1.0-beta.3
candid-core-tsRust librarycrates/candid-core-ts/Cargo.tomlnone, by publish = false0.0.0
@candid-core/schemanpm packagecrates/candid-core-ts/ts/package.jsonnpm0.2.0; beta: 0.3.0-beta.1
@candid-core/clinpm packagecrates/candid-core-wasm/npm/package.jsonnpm0.1.0; beta: 0.2.0-beta.1
candid-core-wasmRust librarycrates/candid-core-wasm/Cargo.tomlnone, by publish = false0.1.0
candid-core-fuzzFuzz harnessfuzz/Cargo.tomlnone, by publish = false0.0.0

Four of those seven rows resolve from a package manager today, and two of them are the same crate. The rest are tracked, buildable and gated, and each carries a deliberate publish = false, one of them because its registry name has not been decided yet. All of them exist in the repository; none of them will install from a registry.

Everything here is pre-1.0

The Rust crate is a prerelease, and the npm packages are 0.2.0 (@candid-core/schema) and 0.1.0 (@candid-core/cli) under latest, with the betas 0.3.0-beta.1 and 0.2.0-beta.1 under beta. Until 1.0, any release may change the public API, the serialized document shapes, the canonical bytes, and therefore every identity computed over them. Pin an exact version in both ecosystems. See Status, versions and releases.

Published as a beta

The table above shows latest and beta. The sections below describe the packages as the repository builds them, published under the npm beta dist-tag as @candid-core/schema 0.3.0-beta.1 and @candid-core/cli 0.2.0-beta.1: latest 0.2.0 still has two more subpaths and an optional peer, and latest 0.1.0 still emits the old module layout. Migrating from 0.2.0 lists every difference, and each is recorded in the package changelogs under 0.3.0-beta.1 and 0.2.0-beta.1.

Which one do I need?#

What you are trying to doReach forWhere to read next
Turn a .did file into a validated Contract from Rustcandid-core, default featuresCompiling Candid sources
Consume Contracts someone else produced, with no Candid parser in your dependency graphcandid-core, default-features = falseInstall and feature surfaces
Get a Contract document on the command line, no Rust code of your ownthe candid-core binaryThe candid-core binary
Validate, encode or decode Candid values from TypeScript@candid-core/schemaQuickstart: TypeScript
Build schemas at runtime from a Contract document, with no code generation step@candid-core/schema, subpath ./contractSchemas at runtime
Emit a TypeScript module from a Contract inside your own Rust toolcandid-core-ts, path or git dependencyThe code generator
Do all of the above from a JavaScript-only project@candid-core/cli@candid-core/cli
Drive an untrusted-input boundary with a fuzzercandid-core-fuzzthe section below

candid-core, the Rust library#

The root package of the workspace, and the only thing here published on crates.io. It owns the Contract model, validation, canonicalization, the content-addressed identities, the limits and cancellation machinery, and the structured diagnostics. With the compiler feature on, it also owns the projection from Candid source into that model. It is for anyone who needs a Candid interface as data rather than as text: a registry that pins interfaces, a tool that detects drift, a service that accepts .did files it did not write.

  • Manifest: Cargo.toml at the repository root.
  • Published: yes, on crates.io. Released versions are 0.1.0-beta.1, 0.1.0-beta.2 and 0.1.0-beta.3; none are yanked. API documentation is on docs.rs.
  • Edition 2021, MSRV rust-version = "1.78", licensed Apache-2.0.

Depending on the crate#

tomlCargo.toml
[dependencies]
candid-core = "=0.1.0-beta.3"
A caret requirement will not resolve

A bare version string such as "0.1" is a caret requirement, and it selects nothing here. Cargo's caret requirement never matches a prerelease, and every published version so far is one. Write the exact requirement "=0.1.0-beta.3", exactly as above.

To take the model without the Candid engine, turn the defaults off:

tomlCargo.toml
[dependencies]
candid-core = { version = "=0.1.0-beta.3", default-features = false }

The four feature surfaces#

One package, four build surfaces. default = ["compiler", "filesystem-compiler", "host-value"], so the dependency line above gives you all of it. The base surface is what remains once default-features = false removes every feature. It is not a named feature; it is the floor.

SurfaceWhat it addsWhat enters your graph
base Contract, ContractDraft, RawContract, validation, canonicalization, the identities, Limits / RuntimeContext, the diagnostics types, ContractEnvelope, artifact_id_with_limits and artifact_id_with_context No Candid engine, no filesystem crate
compiler compile_did, compile_with_resolver, Compilation, SourceInfo, SourceId / SourceResolver / MemoryResolver candid =0.10.30, candid_parser =0.4.0
filesystem-compiler WorkspaceResolver, compile_did_file, and the candid-core binary. Implies compiler. cap-std =4.0.2, and only on targets that are not target_os = "unknown"
host-value HostValue and validate_host_value ic_principal =0.1.5

A disabled feature removes its API rather than leaving a stub that fails later. The full account, including what feature splitting does and does not bound in a workspace, is on Install and feature surfaces.

candid-core, the command-line binary#

The same published crate also ships a binary, declared as [[bin]] name = "candid-core" at src/bin/candid-core.rs with required-features = ["filesystem-compiler"]. It is for the developer who wants a Contract document on disk and does not want to write Rust to get one. It fits a build step, a CI check, or a drift comparison run by hand.

Because filesystem-compiler is part of the default set, an ordinary install builds it. From the published crate:

bash
cargo install candid-core --locked --version '=0.1.0-beta.3'
candid-core compile ./service.did

From a checkout of the repository, without installing anything:

bash
cargo run --bin candid-core -- compile ./service.did
cargo run --bin candid-core -- compile ./service.did --envelope
cargo run --bin candid-core -- validate ./contract.json

It accepts exactly this grammar and nothing else:

textsrc/bin/candid-core.rs
candid-core compile <path> [--no-source-info | --envelope]
candid-core validate <path>

Every non-usage outcome is one pretty-printed JSON document on stdout with an empty stderr. A failure is exit 1 and {"ok": false, "diagnostics": …}. Success is exit 0: plain compile and validate print "ok": true alongside the contract, while compile --envelope prints the envelope document itself — {"contract": …, "extensions": …}, with no ok key — so the output can be saved and consumed whole. Anything outside the grammar exits 64, prints the usage text on stderr, and writes nothing to stdout. The candid-core binary has the full contract.

candid-core-ts, the TypeScript generator#

A Rust library that reads a validated Contract and returns the complete text of one TypeScript module: for every declaration, a hand-readable type alias and a runtime schema object annotated so that tsc itself proves the two agree. It is for whoever is building code generation into their own tool, a build script or a CLI or an editor plugin, rather than shelling out to a binary.

  • Manifest: crates/candid-core-ts/Cargo.toml. A member of the root workspace.
  • Published: no, and not by accident. The manifest carries publish = false because a crates.io crate name is as permanent as a version, and candid-core-ts is a working name, not a decision. The gate stands until the name is chosen deliberately.
  • Version 0.0.0, edition 2021, rust-version = "1.78", Apache-2.0.
  • Depends on candid-core at version = "=0.1.0-beta.3" with default-features = false, and a path alongside it so the workspace builds against the sibling. The base surface only, which is the design statement: a generator needs no Candid parser, no filesystem capability and no host-value ABI.

Depending on the generator#

Path or git, or not at all. There is no cargo add line for it and there will not be one until the name question is settled.

tomlCargo.toml
[dependencies]
# Inside a checkout of this repository:
candid-core-ts = { path = "crates/candid-core-ts" }

# Or from git, pinned to a commit:
candid-core-ts = { git = "https://github.com/b3hr4d/candid-core", rev = "<commit sha>" }

That choice travels. crates.io refuses a package with a git dependency outright, and a path dependency must also carry a version requirement the registry can resolve — which candid-core-ts, having no published version, cannot satisfy. So depending on it this way is fine for an application or an internal tool, and means your own crate cannot go to crates.io either. If you need generated TypeScript without that constraint, run the binary or the WebAssembly CLI instead of linking the generator.

What it exposes#

One entry point plus the two inputs it needs:

rustcrates/candid-core-ts/src/lib.rs
pub fn generate_module(
    contract: &Contract,
    names: &TsNames,
    options: &TsOptions,
) -> Result<GeneratedModule, TsGenError>

TsNames is the table of field and variant-arm spellings, keyed by (container, label id). A Contract stores only the numeric Candid label id, so you supply the spellings yourself. Build it with TsNames::new() (every field renders _N_), TsNames::from_pairs, or TsNames::from_source_info. TsOptions carries one field, principal_import. The result is the module text plus omitted, the declarations and actor methods it had to leave out and why; see The code generator.

FeatureWhat it adds
noneThe default. A pure base-surface consumer of candid-core. This featureless build is the boundary claim, and CI checks it on its own.
compilerEnables candid-core/compiler and with it TsNames::from_source_info, the bridge from a compilation's provenance sidecar into the name table.

@candid-core/schema, the npm runtime#

The TypeScript half of the project, and the package most readers will actually install: schema builders with real static inference, structural validation, and a TypeScript-native Candid binary codec. It is for any TypeScript consumer of a canister, with or without a generated module, since the same schemas can be built at runtime from a Contract document.

  • Manifest: crates/candid-core-ts/ts/package.json.
  • Published: yes, on npm. Released versions are 0.0.0-bootstrap, 0.1.0, 0.1.1, 0.2.0 and 0.3.0-beta.1; 0.2.0 is latest, 0.3.0-beta.1 is beta, and both pair with candid-core 0.1.0-beta.3.
  • ESM only ("type": "module"), sideEffects: false, Apache-2.0. The tarball ships dist, README.md, CHANGELOG.md and LICENSE.
  • Versions independently of the Rust crate. Its changelog names the candid-core version each release pairs with.
bash
npm install --save-exact @candid-core/schema@beta

That is the whole install: no runtime dependencies and no peers. Every module is self-contained, principal typing included, so nothing needs the Internet Computer SDK to exist.

The subpath exports#

Each entry resolves to a .d.ts and a .js under dist/, and nothing outside the map is importable. The map is exactly the four below: the package is the Candid layer only, and calling a canister belongs to the layer built on top of it.

SubpathWhat it is
.The schema core: the c builders, the deliberately invariant Schema<in out T>, Infer, the node interfaces walkers narrow on, plus resolveSchema and serviceMethods for reading a schema back, the Principal type (canonical principal text) with principal and isPrincipal, and OptDomain with isBoxedOpt for the boxed-option rule. See Builders and inference.
./validateStructural validation that never throws on any value. Issues carry stable codes and $-rooted paths, plus isResultSchema and unwrapResult for the variant { ok; err } convention. See Validation.
./contractschemaFromContract: build the same schemas at runtime from a canonical Contract JSON document, or from a one-document ContractEnvelope carrying its field-name table. See Schemas at runtime.
./codecThe Candid binary wire format, schema-directed, with the specification's coercion relation on decode and explicit resource budgets. See Binary codec.

@candid-core/cli, the WebAssembly on-ramp#

bash
npx @candid-core/cli@0.2.0-beta.1 gen ./service.did -o ./generated

It gives a JavaScript-only project the whole pipeline with no Rust toolchain. The candid-core compiler surface and the candid-core-ts generator are compiled together into one WebAssembly artifact, wrapped in a Node CLI and a small JS module that also runs in a browser. Both library calls are data in, data out: one JSON-serialisable request to the WebAssembly module, its parsed JSON response returned verbatim, no eval, and no filesystem access from the WebAssembly side.

  • Manifest: crates/candid-core-wasm/npm/package.json.
  • Binary name: candid-core-cli, at ./bin/cli.js.
  • Exports: "." → ./lib/index.js under a types condition, plus ./package.json. ESM only, Apache-2.0, no runtime dependencies; the single peer, @candid-core/schema at exactly 0.3.0-beta.1 (^0.2.0 in the published 0.1.0), is declared optional. The one devDependency is playwright-core for the browser tests.
  • CLI grammar: candid-core-cli gen <service.did>... [-o <dir>] [--json] [--check] (0.1.0 takes one entry and neither flag). -o defaults to the current working directory. Output names come from each entry file's stem: service.did writes service.ts and service.envelope.json, and prints the content-addressed identities.
  • Library exports: didToContract(sources) and didToModule(sources), each taking either a string of Candid text or a { entry, files } bundle, plus init(input), which loads the embedded WebAssembly module once and needs no argument under Node.

Building it from source#

To work on the package itself, build it from a clone. The wasm/ directory is build output and is not committed, so the first step is always to produce it. wasm-pack 0.14.0 is the version the repository's own workflows install.

  1. Get the toolchain

    bash
    rustup target add wasm32-unknown-unknown
    cargo install wasm-pack --version 0.14.0 --locked
  2. Build the WebAssembly artifact

    bash
    cd crates/candid-core-wasm
    wasm-pack build --target web --out-dir npm/wasm --no-pack --release -- --locked
  3. Run the CLI from the package directory

    bash
    cd npm
    node ./bin/cli.js gen ./service.did -o ./generated

That writes generated/service.ts and generated/service.envelope.json. Details of both, and of the failure shapes, are on @candid-core/cli.

candid-core-wasm, the crate behind the CLI#

The Rust crate that becomes that artifact. It is a thin wasm-bindgen shim over two things it does not implement: candid-core's compiler surface (compile_with_resolver plus MemoryResolver, so no filesystem is ever touched from the WebAssembly side) and the candid-core-ts generator. It exposes two functions, both string in and string out:

rustcrates/candid-core-wasm/src/lib.rs
#[wasm_bindgen(js_name = didToContract)]
pub fn did_to_contract(request: &str) -> String

#[wasm_bindgen(js_name = didToModule)]
pub fn did_to_module(request: &str) -> String
  • Manifest: crates/candid-core-wasm/Cargo.toml. Version 0.1.0, publish = false, Apache-2.0, crate-type = ["cdylib", "rlib"].
  • Its own workspace root. The manifest carries a bare [workspace] table and a tracked Cargo.lock of its own, and the root manifest names it in exclude = ["fuzz", "crates/candid-core-wasm"]. That is the point: wasm-bindgen never enters the main workspace's resolved graph or its feature unification, and cargo metadata over the published crate never sees it.
  • Dependencies: candid-core and candid-core-ts by path, serde_json =1.0.150, and wasm-bindgen =0.2.108 only under cfg(target_arch = "wasm32").

You do not depend on this crate; you build it. wasm-opt is switched off in the manifest on purpose, so the emitted bytes are a function of the pinned rustc and wasm-bindgen alone with no binaryen download in the build path.

candid-core-fuzz, the fuzz harness#

Internal, and the only unit here that never enters a consumer's build. Each target drives one untrusted-input boundary with arbitrary bytes.

  • Manifest: fuzz/Cargo.toml. Version 0.0.0, publish = false, [package.metadata] cargo-fuzz = true.
  • Its own workspace root with its own tracked fuzz/Cargo.lock, excluded from the root workspace for the same reason as candid-core-wasm. Dependencies are exact-pinned to match the crate under test, libfuzzer-sys =0.4.13 and serde_json =1.0.150, because the library's observable decode behaviour depends on serde_json's recursion limit.
  • Features mirror the library's: default = ["compiler", "filesystem-compiler", "host-value"], each forwarding to the matching candid-core feature, so a target is only built when the feature owning the API it drives is on.
  • Seven targets: source_parsing, contract_json, canonicalization, resolver_ids, provenance, host_value and envelope_json.

Run one from the repository root, passing the writable corpus directory first so the tracked seeds and regressions stay read-only:

bashfuzz/README.md
cargo +nightly fuzz run contract_json \
  fuzz/corpus/contract_json fuzz/seeds/contract_json fuzz/regressions/contract_json \
  -- -max_total_time=60

What ships and what does not#

The published .crate archive is governed by a positive allowlist in the root Cargo.toml, not a growing list of exclusions. A newly tracked file is outside the archive until it is named in include on purpose. What is named: /src/**/*.rs, /examples/**/*.rs, /docs/**/*.md, /README.md, /CHANGELOG.md and /LICENSE.

So tests/, benches/, crates/, fuzz/ and .github/ never reach a consumer's registry download. That means candid-core-ts, candid-core-wasm and candid-core-fuzz are reachable only from a git checkout, whatever their publish setting says. The release and release-candidate workflows run tests/fixtures/packaging/verify_package_manifest.py, which asserts both halves of that policy: every required path present, every forbidden path absent, and no unexplained extra.