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.
.did source#
Your Candid interface and every file it imports. Text, with whitespace, comments and declaration spellings that mean nothing to the wire.
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.
Contract document#
Canonical JSON: types, declarations, an optional actor, and identities. Optionally wrapped in a ContractEnvelope that also carries the field-name table.
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#
| Unit | Kind | Manifest | Registry | Version |
|---|---|---|---|---|
candid-core | Rust library | Cargo.toml | crates.io | 0.1.0-beta.3 |
candid-core (binary) | CLI, same crate | Cargo.toml [[bin]] | crates.io | 0.1.0-beta.3 |
candid-core-ts | Rust library | crates/candid-core-ts/Cargo.toml | none, by publish = false | 0.0.0 |
@candid-core/schema | npm package | crates/candid-core-ts/ts/package.json | npm | 0.2.0; beta: 0.3.0-beta.1 |
@candid-core/cli | npm package | crates/candid-core-wasm/npm/package.json | npm | 0.1.0; beta: 0.2.0-beta.1 |
candid-core-wasm | Rust library | crates/candid-core-wasm/Cargo.toml | none, by publish = false | 0.1.0 |
candid-core-fuzz | Fuzz harness | fuzz/Cargo.toml | none, by publish = false | 0.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.
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.
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 do | Reach for | Where to read next |
|---|---|---|
Turn a .did file into a validated Contract from Rust | candid-core, default features | Compiling Candid sources |
| Consume Contracts someone else produced, with no Candid parser in your dependency graph | candid-core, default-features = false | Install and feature surfaces |
| Get a Contract document on the command line, no Rust code of your own | the candid-core binary | The candid-core binary |
| Validate, encode or decode Candid values from TypeScript | @candid-core/schema | Quickstart: TypeScript |
| Build schemas at runtime from a Contract document, with no code generation step | @candid-core/schema, subpath ./contract | Schemas at runtime |
| Emit a TypeScript module from a Contract inside your own Rust tool | candid-core-ts, path or git dependency | The 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 fuzzer | candid-core-fuzz | the 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.tomlat the repository root. - Published: yes, on crates.io. Released versions are
0.1.0-beta.1,0.1.0-beta.2and0.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#
[dependencies]
candid-core = "=0.1.0-beta.3"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:
[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.
| Surface | What it adds | What 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:
cargo install candid-core --locked --version '=0.1.0-beta.3'
candid-core compile ./service.didFrom a checkout of the repository, without installing anything:
cargo run --bin candid-core -- compile ./service.did
cargo run --bin candid-core -- compile ./service.did --envelope
cargo run --bin candid-core -- validate ./contract.jsonIt accepts exactly this grammar and nothing else:
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 = falsebecause a crates.io crate name is as permanent as a version, andcandid-core-tsis 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-coreatversion = "=0.1.0-beta.3"withdefault-features = false, and apathalongside 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.
[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:
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.
| Feature | What it adds |
|---|---|
| none | The default. A pure base-surface consumer of candid-core. This featureless build is the boundary claim, and CI checks it on its own. |
| compiler | Enables 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.0and0.3.0-beta.1;0.2.0islatest,0.3.0-beta.1isbeta, and both pair withcandid-core0.1.0-beta.3. - ESM only (
"type": "module"),sideEffects: false, Apache-2.0. The tarball shipsdist,README.md,CHANGELOG.mdandLICENSE. - Versions independently of the Rust crate. Its changelog names the
candid-coreversion each release pairs with.
npm install --save-exact @candid-core/schema@betaThat 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.
| Subpath | What 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. |
| ./validate | Structural 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. |
| ./contract | schemaFromContract: 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. |
| ./codec | The 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#
npx @candid-core/cli@0.2.0-beta.1 gen ./service.did -o ./generatedIt 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.jsunder atypescondition, plus./package.json. ESM only, Apache-2.0, no runtime dependencies; the single peer,@candid-core/schemaat exactly0.3.0-beta.1(^0.2.0in the published0.1.0), is declared optional. The one devDependency isplaywright-corefor the browser tests. - CLI grammar:
candid-core-cli gen <service.did>... [-o <dir>] [--json] [--check](0.1.0takes one entry and neither flag).-odefaults to the current working directory. Output names come from each entry file's stem:service.didwritesservice.tsandservice.envelope.json, and prints the content-addressed identities. - Library exports:
didToContract(sources)anddidToModule(sources), each taking either a string of Candid text or a{ entry, files }bundle, plusinit(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.
-
Get the toolchain
bashrustup target add wasm32-unknown-unknown cargo install wasm-pack --version 0.14.0 --locked -
Build the WebAssembly artifact
bashcd crates/candid-core-wasm wasm-pack build --target web --out-dir npm/wasm --no-pack --release -- --locked -
Run the CLI from the package directory
bashcd 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:
#[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. Version0.1.0,publish = false, Apache-2.0,crate-type = ["cdylib", "rlib"]. - Its own workspace root. The manifest carries a bare
[workspace]table and a trackedCargo.lockof its own, and the root manifest names it inexclude = ["fuzz", "crates/candid-core-wasm"]. That is the point:wasm-bindgennever enters the main workspace's resolved graph or its feature unification, andcargo metadataover the published crate never sees it. - Dependencies:
candid-coreandcandid-core-tsby path,serde_json =1.0.150, andwasm-bindgen =0.2.108only undercfg(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. Version0.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 ascandid-core-wasm. Dependencies are exact-pinned to match the crate under test,libfuzzer-sys =0.4.13andserde_json =1.0.150, because the library's observable decode behaviour depends onserde_json's recursion limit. - Features mirror the library's:
default = ["compiler", "filesystem-compiler", "host-value"], each forwarding to the matchingcandid-corefeature, 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_valueandenvelope_json.
Run one from the repository root, passing the writable corpus directory first so the tracked seeds and regressions stay read-only:
cargo +nightly fuzz run contract_json \
fuzz/corpus/contract_json fuzz/seeds/contract_json fuzz/regressions/contract_json \
-- -max_total_time=60What 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.
The four surfaces in depth, and what each one adds to your dependency graph.
TypeScript How the TypeScript side fits togetherGenerator, schema runtime, codec and Contract loader, and the order to read them in.
Project Status, versions and releasesWhere each unit is in its life cycle, what is settled, and what is still moving.