FAQ — General
This page answers the questions we get from people who are evaluating causl for the first time: what it is, what makes it different from the libraries they already know, when it's the right tool, and what to expect about compatibility, language support, and the road to 1.0. Each answer expands beneath the question; cross-links point at the SPEC, the API reference, or the relevant topic guide for the full story.
About causl
What is causl?
Causl is a transactional state-management engine for JavaScript
and TypeScript. You declare a graph of inputs (mutable
cells) and derived nodes (pure functions of other
nodes), and the engine guarantees that every observable read
across the graph corresponds to a single, atomic commit. Writes
go through graph.commit(fn); if any precondition
fails, the whole transaction rolls back and no subscriber is
ever shown a partial update.
import { createCausl, input, derived } from '@causl/core';
const graph = createCausl();
const count = input(graph, 0);
const doubled = derived(graph, g => g.read(count) * 2);
graph.commit(() => {
graph.set(count, 5);
});
console.log(graph.read(doubled)); // 10
The engine is described in full in
SPEC.md,
and the public surface for each @causl/* package is
documented in the
generated API reference.
What makes causl different from Redux, Jotai, or MobX?
The short version: causl treats the dependency graph as a first-class object and runs every batch of writes as a true transaction. The longer version splits across three axes — the consistency model, the failure model, and what you can prove statically.
| Concern | Redux | Jotai | MobX | causl |
|---|---|---|---|---|
| Atomic batched writes | Per-action only | No | Per-action only | Yes — commit() is all-or-nothing |
| Glitch-free derivations | Manual via selectors | Best-effort | Best-effort | Theorem — Phase D Kahn topo |
| Static race detection | No | No | No | causl-check — twelve passes |
| Bounded model checker | No | No | No | causl-enumerate |
Concretely, every commit goes through the Phase A–H pipeline (SPEC §5.1): a precondition check, the input-write phase, the input-layer fast path, a Kahn topological recompute, frozen Commit assembly, commit-history append, subscriber dispatch, and transient disposal. Subscribers never observe a mid-commit state — that property is enforced by the engine, not by convention. See the README for the comparison written out at length.
Detailed explanation. Most JS state libraries can give you batching, and most can give you derivations, but which value of a derived node is consistent with which value of an input is only well-defined inside a causl commit. Outside a commit, reads observe the last fully-committed graph time — the engine never lets the application observe an intermediate state. This is what makes glitch-freedom a theorem and not a scheduler trick; see docs/semantics.md for the denotational walkthrough.
When should I choose causl over a simpler library?
Causl is the right tool when at least one of these is true for your application:
- Tangled dependency graphs. You have many derived values that depend on each other, and the wrong recompute order produces visibly wrong results — spreadsheets, formula engines, planners, BI dashboards, simulators.
- Atomic writes. A user action mutates more than one cell, and any observer that sees only some of the writes is a bug — multi-row edits, optimistic mutations with rollback, undo / redo stacks.
- Race-class auditability. You need to be able to argue at code-review time that no derivation can ever observe a torn read or a stale fetch — regulated UIs, instrument panels, financial dashboards.
If your state model is "one global object, a handful of
selectors, no concurrent fetches" then Redux Toolkit, Zustand,
or plain useState are likely the right answer. The
project home
lists the cases where causl is not the right tool;
we'd rather you pick something simpler when it fits.
What's the license? Can I use it commercially?
Causl is MIT-licensed. Every package under @causl/*,
the two Rust tools under tools/, the documentation
site, and the benchmark harness are all MIT. There are no
patent grants, dual-license carve-outs, or contributor
agreements beyond the standard DCO sign-off. You can use causl
in commercial, closed-source, or open-source projects without
asking us. See
LICENSE
and the per-package package.json for the exact
text.
Compatibility
Which browsers does causl support?
The npm packages target ES2022 and ship as ESM. That means modern evergreen browsers — current Chrome, Edge, Firefox, and Safari — work out of the box. There is no polyfill bundle; if you need to support older environments, run causl through your existing transpile pipeline (Vite, esbuild, Rollup, Webpack, Next.js, Remix — all tested in CI). The engine has no runtime dependency on a specific DOM API, so anywhere your bundler can produce ES2022 output, causl will run.
The optional WasmBackend requires the
WebAssembly global, which has been available in
every shipping browser since 2017. The WASM module is gated
behind loadWasmBackend() — you only pay the
download and instantiation cost if you opt in.
Which Node versions are supported?
The supported matrix is Node 20.x (LTS) and Node 22.x (current LTS). CI runs every test suite on both. Earlier versions may work for trivial usage but are not gated; ESM-only consumers in particular should be on Node 20+ to avoid loader corner cases. Deno and Bun are not tested in CI but the engine is dependency-free at runtime, so it generally works there too.
Do I have to use TypeScript?
No — causl is published with .d.ts declarations
alongside the JavaScript, so plain-JS consumers get full
autocompletion and inline docs in any editor that consumes type
declarations (VS Code, WebStorm, Neovim with the TypeScript LSP).
That said, the engine was designed type-first: input cells,
derived nodes, and commits carry their value types through the
graph, and that's where most of the day-to-day ergonomics come
from.
// TypeScript narrows the value type all the way through.
const name = input(graph, 'Ada'); // input<string>
const upper = derived(graph, g =>
g.read(name).toUpperCase() // typed as string
);
The minimum supported TypeScript version is 5.4.
Older versions may compile against the type declarations but the
stricter inference signatures used in @causl/sync
and @causl/persistence require 5.4 or newer.
Does causl work with server-side rendering?
Yes. The engine has no implicit globals — every graph is a
first-class value returned by createCausl() — so
it works in any server environment that runs JavaScript: Node,
Workers, Deno, Bun, edge runtimes. For React SSR, the
@causl/react bindings expose a snapshot/hydrate
flow that lets the server build a graph, serialise its frozen
Commit, and rehydrate it on the client.
// Server
import { createCausl, snapshot } from '@causl/core';
const graph = createCausl();
// ... populate inputs from request data ...
const payload = snapshot(graph);
return { props: { causl: payload } };
// Client
import { hydrate } from '@causl/core';
const graph = hydrate(payload);
Snapshot/hydrate is covered in detail under
@causl/core
and SPEC §14. Per-package SSR notes for React, Next.js, and
Remix integrations live in the
Usage Guide.
Does causl handle async data and fetches?
Yes, but through a dedicated package —
@causl/sync —
because async resources have their own consistency model. The
engine itself is synchronous and transactional by design; the
moment you let a derivation suspend mid-commit you lose
atomicity. Instead, @causl/sync wraps fetches in a
composite statechart (launch, version-stamp, abort, settle) and
lifts the outcome back into the graph as an input write.
import { createCausl, input } from '@causl/core';
import { resource } from '@causl/sync';
const graph = createCausl();
const userId = input(graph, 'ada');
const user = resource(graph, {
key: g => g.read(userId),
fetch: async (id, signal) => {
const res = await fetch(`/api/users/${id}`, { signal });
return res.json();
},
});
Cancellation, version-stamping (so a slow response from a stale key never lands), and conflict resolution are all governed by the statechart in SPEC.async.md. The race classes S-1 and S-2 in §16 are the ones that motivate the version-stamp; if you are wiring up your own async layer, read those two rows first.
Detailed explanation. The contract is that
@causl/sync never blocks a commit. When a fetch
settles, the resolver enqueues a follow-up commit that writes
the result to a dedicated input. The graph stays
transactional; the fetch lifecycle stays explicit.
Releases and roadmap
When does causl reach 1.0?
The current published version is 0.9.0. The 0.x series is feature-complete for the eight commitments that define the engine; what remains for 1.0 is a stability guarantee. The release will happen when one of two conditions holds:
-
The Rust engine port lands. It completes its
GO/NO-GO review and the
WasmBackendgraduates from the current Phase-1 TypeScript-wrapper into the native Rust core. That gives 1.0 a single normative reference implementation across both backends and unblocks the long-term performance roadmap. The Rust core and its WASM backend are an Enterprise feature Enterprise. -
Or: the maintainers explicitly declare API
stability without waiting for the Rust port. That happens if
the public surface across
@causl/core,@causl/react,@causl/sync, and@causl/persistencegoes through a full minor-release cycle (currently scheduled for 0.10 and 0.11) without a breaking change, and the SPEC corpus passes the bounded model checker at Tier-3 bounds on the next two scheduled runs.
In practice that means 1.0 is gated on engineering signal, not
on a calendar date. The 0.9 series is API-frozen except for
strictly additive changes; if you adopt 0.9 today, the upgrade
to 1.0 is expected to be either a no-op or a single codemod.
The
CHANGELOG
tracks every wave shipped on main and is the source
of truth for "what changed since I last upgraded".
How are bugs and behavior changes handled?
Every behavior is normative in SPEC.md. If the engine diverges from the SPEC, that's the bug — we change the engine, not the SPEC. If the SPEC itself needs to change, the PR that proposes the change is required to update the prose, the type surface, the conformance tests, the static checker, and the bounded model checker in lockstep. The amendment process is documented in CONTRIBUTING.md.
One commonly-cited example: graph.read() reference
identity is not contractually guaranteed
(SPEC §15.1). If you cache or compare derived results by
reference, see the H1 hazard note in the
Best Practices guide.
Where do I ask follow-up questions?
For usage questions, the
GitHub
Discussions board. For bugs, the
issue tracker
— please include a minimal reproduction and the output of
graph.explain() from
@causl/devtools if the issue involves a derivation
or a commit. For real-time questions, join the community chat
linked from the
project home.
Next steps
- Getting Started — install causl and write your first commit.
- Tutorial — build a working app step-by-step.
- Usage Guide — one chapter per topic, in narrative form.
- API reference — generated from the source on every build.
- SPEC.md — the canonical specification.