Installation
Causl ships as private packages under the @causl scope, served by the Gitea
npm registry at https://git.opsite.ca/api/packages/causl/npm/. The client
package is @causl/causl-wasm-ts, and its engine is the Rust core compiled to
WebAssembly. I put the registry configuration first because it is the step that fails
quietly: without the scoped registry line, npm install @causl/core still
succeeds, against a different package on public npmjs.
Read this before you copy any install command.
@causl/corehas zero versions on the Gitea registry, and it does resolve on public npmjs at 0.3.0 through 0.3.3 as a different package: the retired TypeScript engine. An install of it returns exit code 0 and prints no warning, then everyimportin your application resolves against the wrong code. The package you want is@causl/causl-wasm-ts. See the@causl/corehazard.
Registry configuration
The @causl scope is private and is not served by npmjs. Point the scope at the
Gitea registry and supply a token before you install anything:
# ~/.npmrc, or an .npmrc at your repo root
@causl:registry=https://git.opsite.ca/api/packages/causl/npm/
//git.opsite.ca/api/packages/causl/npm/:_authToken=${GITEA_PACKAGES_TOKEN}
always-auth=true
GITEA_PACKAGES_TOKEN is a Gitea personal access token carrying the
read:package scope, issued from
git.opsite.ca → Settings → Applications → Generate New Token.
Anonymous requests to the registry return 401, so a missing token is a loud
failure. A missing registry line is the quiet one.
The old Verdaccio host iasbuilt-npm.opsite.ca is gone. Any
.npmrc snippet that names it, in your repo or in an older copy of these docs,
is stale and should be replaced with the block above.
The @causl/core hazard
This is the single most expensive mistake available on this page, because nothing about it looks like a mistake at the time:
@causl/corehas zero versions published to the Gitea registry. There is no such package in the private scope.@causl/coreis live on public npmjs at 0.3.0 through 0.3.3, as a different package: the retired TypeScript engine.- So without the
@causl:registry=line, npm reaches npmjs, resolves that package, installs it, and reports success. There is no error to notice, and no version pin that makes the difference visible.
The correct client package is @causl/causl-wasm-ts. If an older document, a
lockfile, or a stale pin in your package.json still names
@causl/core, that is the thing to fix, and a successful install is not evidence
that it is fine.
One thing that is not the same mistake: the open-source TypeScript engine on public
npmjs is @causlts/core, from
causljs/causl-ts. That is a
genuinely different product with its own release line, not a mirror of the private client.
Do not treat the two as interchangeable, and do not substitute one for the other to work
around a registry problem.
@causl/wasm and @causl/checker are not published anywhere. If you
find an install command for either, it is out of date.
Supported environments
Causl ships ESM-only modules. Because the engine is WebAssembly, the host floor is set by what the shipped artefact needs rather than by the JavaScript language level:
- Node 22+ (
engines.node: ">=22") for server-side, SSR, and tooling use. - Safari 18.2+, Chromium 119+, Firefox 120+ in the browser.
- TypeScript with
moduleResolutionset to"Bundler"or"NodeNext". The repo itself builds on TypeScript 5.9 and ships hand-written.d.tsdeclarations alongside the JavaScript. - React 18.3+ or React 19 for
@causl/react(peer dependency).
The governing browser requirement is typed function references, established by feature-bisecting the shipped artefact. The artefact declares zero WasmGC struct, array, or rec-group types, so "WasmGC heap types" is not the requirement and an older floor quoting it was wrong.
Treat the floor as a release-note claim, not as our measurement. Nobody has run the shipped artefact on a real boundary host at Safari 18.2, Chromium 119, or Firefox 120. If your deployment targets sit on that edge, measure before you commit.
There is no CommonJS build. If you need to require the client from a CJS module, use the
dynamic import() form or a small wrapper.
Engine
There is one engine. @causl/causl-wasm-ts runs the Rust core compiled to
WebAssembly (rust-ssot) unconditionally, and there is no TypeScript fallback in
the package: createCauslTs is deleted from source, and
createCausl() is construct-or-throw. The implicit capability fallback that
earlier revisions of these docs described was withdrawn at 0.5.0, both because the fallback
engine and the primary disagreed on a surface the SPEC requires to be identical, and because
the implicit path was the accident path.
Booting it is one await at application init, and nothing after that:
import { createCausl } from '@causl/causl-wasm-ts';
import { preloadCauslWasm } from '@causl/causl-wasm-ts/wasm';
await preloadCauslWasm(); // once, at app init
const graph = createCausl(); // synchronous from here on
const a = graph.input('a', 1);
const b = graph.input('b', 2);
const sum = graph.derived('sum', get => get(a) + get(b));
graph.commit('bump-a', tx => {
tx.set(a, 10);
});
console.log(graph.read(sum)); // 12
The wasm artefact sits behind the explicit @causl/causl-wasm-ts/wasm subpath,
and since 0.5.0 importing it is required rather than optional. Importing the package root
pulls in the public API plus a small loader stub and no engine; the engine bytes are paid for
by the caller that imports the subpath and calls preloadCauslWasm(), which every
application now does. createCauslWasmSync() is available for synchronous
construction sites, and createCauslWasm() is retained as preload composed with
the sync factory.
Two failure codes matter, and you should branch on error.code rather than
instanceof:
CAUSL_WASM_ENGINE_UNAVAILABLE: the subpath was never imported, or WasmGC cannot instantiate on this host.CAUSL_WASM_NOT_PRELOADED: the subpath was imported, butpreloadCauslWasm()never resolved.
One bridge ships, gc-classic. The gc-builtins bridge was deleted.
Bridge ids are gc-classic and gc-builtins, never
wasmgc-classic or wasmgc-builtins.
@causl/causl-wasm-ts
@causl/causl-wasm-ts is the client: the dependency graph, the transactional
commit, the deterministic recompute pipeline, and the loader that installs the Rust engine
behind them. Every other package peers on it. Start here; install nothing else until you
need it.
# npm
npm install @causl/causl-wasm-ts
# pnpm
pnpm add @causl/causl-wasm-ts
# yarn
yarn add @causl/causl-wasm-ts
The only declared peer dependency is fast-check, and it is marked optional: it
is pulled in only by the @causl/causl-wasm-ts/testing subpath that exposes
property-test helpers. You will never need to install it for production use.
The package exposes four entry points: the root, /wasm (the loader and
preloadCauslWasm), /testing, and /internal.
@causl/react
@causl/react wires a causl graph into React's render lifecycle through
useSyncExternalStore. The headline hook is useCauslNode, with
useCauslShallow, useCauslFamily, useCauslSuspense,
and a <Hydrate> SSR boundary alongside it.
# npm
npm install @causl/causl-wasm-ts @causl/react react
# pnpm
pnpm add @causl/causl-wasm-ts @causl/react react
# yarn
yarn add @causl/causl-wasm-ts @causl/react react
@causl/react declares both react (at
^18.3.0 || ^19.0.0) and @causl/causl-wasm-ts as
peer dependencies. Nothing is resolved for you transitively, so install the
client explicitly alongside every companion package, exactly as the commands above do. The
same holds for @causl/sync: if you want
useCauslSuspense against async resources, install the sync package by hand.
@causl/sync
@causl/sync is the async resource lifecycle: fetchers, mutations,
suspendable reads, and the race-row guarantees (S-1 stale-write, S-2 stale-read) that
keep concurrent network calls from corrupting derived state. Install it when you have
data that lives outside the graph, over HTTP, IndexedDB, WebSocket, or anything similar.
# npm
npm install @causl/causl-wasm-ts @causl/sync
# pnpm
pnpm add @causl/causl-wasm-ts @causl/sync
# yarn
yarn add @causl/causl-wasm-ts @causl/sync
@causl/persistence
@causl/persistence ships the storage adapters (localStorage,
sessionStorage, IndexedDB, custom StorageAdapter) that pair
with graph.dehydrate() and graph.hydrate() in the client. The
protocol ships in the client; the concrete adapters live here so the
client stays small.
# npm
npm install @causl/causl-wasm-ts @causl/persistence
# pnpm
pnpm add @causl/causl-wasm-ts @causl/persistence
# yarn
yarn add @causl/causl-wasm-ts @causl/persistence
@causl/checker
@causl/checker is the static analyser: a CLI plus library entry point that
walks your causl graph definitions and surfaces race rows (R-1 through R-8), cycle
hazards, missing capability scopes, and unreachable derivations.
There is nothing to install today.
@causl/checkeris not published, to the Gitea registry or anywhere else, so anynpm install @causl/checkerline you find is wrong. Consume the checker from its source repository until a published artefact exists, and do not add it to apackage.jsonexpecting it to resolve.
@causl/formula
@causl/formula brings spreadsheet formula patterns on top of the client:
formulas, ranges, and cycles expressed as causl derivations. Install it when your
application has spreadsheet-like cells with formula references or range dependencies
that you want expressed declaratively rather than re-encoded as ad-hoc derivations.
# npm
npm install @causl/causl-wasm-ts @causl/formula
# pnpm
pnpm add @causl/causl-wasm-ts @causl/formula
# yarn
yarn add @causl/causl-wasm-ts @causl/formula
@causl/devtools
@causl/devtools is the optional Historian and Editor surface: time-travel,
live derived inspection, batched replaceMany edits during development.
It pairs with the browser extension and is a dev-only dependency.
# npm
npm install --save-dev @causl/devtools
# pnpm
pnpm add -D @causl/devtools
# yarn
yarn add --dev @causl/devtools
The wiring itself is in the client behind a one-line opt-in:
createCausl({ devtools: true }) dead-code-eliminates in production builds.
You only install @causl/devtools if you want the standalone REPL utilities
outside the browser extension.
TypeScript setup
All @causl packages ship .d.ts files. The minimum
tsconfig.json for an application consuming causl looks like this:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
Module resolution. Causl is ESM-only and uses package exports
subpaths (@causl/causl-wasm-ts/wasm,
@causl/causl-wasm-ts/testing, @causl/causl-wasm-ts/internal). You
need "moduleResolution": "Bundler" (or "NodeNext") so TypeScript
reads the exports map. Older "moduleResolution": "Node" will
silently miss the subpath types, which matters most for /wasm, the subpath every
application has to import.
Strict mode. Causl's branded types (NodeId<T>,
GraphTime) only catch the errors they are designed to catch under
strict: true. A non-strict tsconfig erases the brand and a
node from graph A passed to graph B will fail at runtime instead of at
tsc. I treat strict: true as a precondition; do not try to use
causl without it.
JSX. If you use @causl/react, set
"jsx": "react-jsx" (or "react-jsxdev" in dev). The classic
"react" setting still works but is no longer recommended.
Bundle sizes
Causl publishes a size-limit CI gate on every published surface so the
bundle budget is enforceable, not aspirational. The ceilings, per SPEC §14.2 and §17.6,
are:
| Bundle | Ceiling | Notes |
|---|---|---|
@causl/causl-wasm-ts (full import) |
30 KB Brotli | Includes source-mapped error chains, devtools opt-in wiring (eliminated in
production), runtime IR validation in hydrate(), branded
NodeId<T> and GraphTime, and the
dehydrate() / hydrate() SSR pair. The
/wasm subpath is externalised from this cell. |
@causl/causl-wasm-ts (createCausl-only) |
26 KB Brotli | The minimum import surface. Adopters who only register inputs and read derivations sit near this floor. |
@causl/causl-wasm-ts/wasm |
32 KB Brotli | The loader plus the backend wrapper. This is JavaScript only; the engine bytes are the row below. |
wasm bridge gc-classic (raw) Enterprise |
756 KiB raw | The shipped causl_engine_bridge_bg.wasm measured
714,194 bytes on 2026-08-11 (sha256 ab4b62c1…), inside
the 774,144-byte cap. Only this one bridge ships. See the
Enterprise wasm-performance guide for what
those bytes buy. |
@causl/react |
8 KB Brotli | Absorbs the four §8.2 extension hooks plus the dev-mode "Update did not commit" warnings (eliminated in production). |
@causl/sync (full import) |
12 KB Brotli | The resource-only entry point is gated separately and is smaller. |
@causl/devtools-bridge (absent-extension path) |
5 KB Brotli | The bridge logic that the devtools: true opt-in imports.
The present-extension path is larger. |
The CI gate size fails any PR that crosses these ceilings, and a bump needs the
§14.2.1 written team consensus in the PR body or it is rejected. The earlier 4.5 KB, 6 KB and
3 KB ceilings were retired once the team accepted that the small-bundle promise was costing
more in adopter glue than it was earning.
Next steps
With the packages installed, the natural next step is the
Tutorial, a walkthrough that builds a small graph, wires it
into a React component, and adds an async fetch with @causl/sync. From
there:
- Usage Guide: task-oriented recipes for commits, subscriptions, persistence, SSR, and time travel.
- API Reference: the generated reference for every published package.
- Best Practices: race rows, capability scoping, and the H1 reference-identity hazard adopters trip on first.
- FAQ: when causl is the right tool, and where it is not.
- Benchmarks: the live cross-library sweep.