FAQ › General

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:

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:

  1. The Rust engine port lands. It completes its GO/NO-GO review and the WasmBackend graduates 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.
  2. 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/persistence goes 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