Documentation / Tutorial / Part 1
Part 1: Setting Up Your Project
This part of the Essentials Tutorial takes you from an empty
folder to a running TypeScript project with @causl/core installed and
a first graph wired up. By the end you will have committed a value, read it back,
and watched a derived node recompute — the smallest example the engine has to
support, and the foundation everything else in the tutorial builds on.
What you'll learn
- How to scaffold a TypeScript project with Vite (and the equivalent with Create React App).
- Which
@causl/*packages you need now versus later in the tutorial. - The conventional
src/layout we use throughout the rest of the tutorial. - How to call
createCausl(), register an input and a derived node, and run your firstcommit.
1. Scaffold the project
Causl is a plain library — there is no Causl CLI and nothing about your build tooling needs to know it exists. We recommend Vite for new projects because it keeps the cold-start fast and its TypeScript defaults are sensible. If you have a strong preference for Create React App, Next.js, Remix, or a non-React tool like SvelteKit, Causl works there too; the differences are confined to which packages you install in Part 5.
Open a terminal and run:
pnpm create vite@latest incident-tracker --template react-ts
cd incident-tracker
pnpm install
If you prefer npm or yarn, the equivalents are npm create vite@latest …
and yarn create vite …. Either way you should end up with a folder
containing package.json, tsconfig.json, a
src/ directory, and a working pnpm dev command.
Using Create React App? Run
npx create-react-app incident-tracker --template typescript instead.
Everything else in the tutorial is identical — Causl does not care which bundler
compiled it.
2. Install Causl
The engine itself lives in @causl/core. That single package contains
createCausl, the canonical seven-method Graph surface,
and the structured error classes you will branch on. You do not need anything else
to follow Parts 1 through 4:
pnpm add @causl/core
The remaining packages are introduced as the tutorial needs them. For reference, this is the full set you will install across the eight parts:
| Package | Introduced in | Purpose |
|---|---|---|
@causl/core | Part 1 | The engine — createCausl(), inputs, deriveds, commits. |
@causl/react | Part 5 | React bindings — useCauslNode, CauslProvider. |
@causl/sync | Part 6 | Async resource lifecycle helpers. |
@causl/persistence | Part 7 | Snapshot/restore + storage adapters. |
@causl/checker | Part 8 | Static lint + bounded model checker. |
You can install everything up front if you prefer one shopping trip; nothing is pulled into the bundle until you import from it.
3. Lay out the source tree
Causl does not impose a folder convention, but the tutorial uses a layout that
scales well from "single graph in src/state/" up to feature-sliced
applications. Replace Vite's src/ with the following structure:
src/
├── main.tsx # React entry point (added in Part 5)
├── App.tsx # Root component (added in Part 5)
└── state/
├── graph.ts # createCausl() call — the single source of truth
├── inputs.ts # graph.input(...) declarations
├── derived.ts # graph.derived(...) declarations
└── actions.ts # functions that wrap graph.commit(...)
The split matters for two reasons. First, it keeps the shape of your state (inputs and deriveds) physically separate from the transitions that mutate it (commit-wrapping action functions). That is the same separation Redux enforces with slices and dispatched actions, and it makes both halves easier to test in isolation. Second, exporting nodes from dedicated modules makes them tree-shakeable and gives the static checker in Part 8 a stable surface to point at.
4. Create the graph
Open src/state/graph.ts and replace its contents with this:
// src/state/graph.ts
import { createCausl } from '@causl/core'
export const graph = createCausl()
That's the whole entry point. createCausl() returns a fresh
Graph with its own independent GraphTime — there is no
module-level state inside the engine and no implicit singleton. Calling
createCausl() twice gives you two separate universes that cannot see
each other; that property is what makes Causl easy to use in tests and in SSR
contexts where each request needs its own state.
Detailed explanation: why a module-scoped export? Exporting a singleton instance fromgraph.tsmatches thecreateStorepattern Redux apps use. In tests, you instead callcreateCausl()insidebeforeEachto get a fresh graph per test. In SSR (Next.js, Remix), you create one per request and pass it through React context — exactly theCauslProvidershape we wire up in Part 5.
5. Add your first input and derivation
Now register a single mutable cell and a computed view over it. Create
src/state/inputs.ts:
// src/state/inputs.ts
import { graph } from './graph'
export const count = graph.input('count', 0)
And src/state/derived.ts:
// src/state/derived.ts
import { graph } from './graph'
import { count } from './inputs'
export const doubled = graph.derived('doubled', g => g(count) * 2)
Two things are worth pausing on. First, both graph.input() and
graph.derived() take a stable string id as their first argument. That
id lives in your information-model namespace, separate from the variable
name — so even after a refactor, the same logical cell still has the same id, and
the bounded model checker can identify it across runs. Part 2 covers the id rules
in detail.
Second, the second argument to graph.derived() is a closure that
receives a tracked accessor (bound to g above — the single-letter
form is idiomatic for inline arrows; any name works). The engine infers the
derivation's dependency set from the accessor calls it observes — there is no manual
"depends on" list to keep in sync. A derivation that switches inputs on an
if branch naturally rewires its dependency set on the next
evaluation.
6. Run your first commit
The only operation that advances GraphTime and writes to inputs is
graph.commit(). Add src/state/actions.ts:
// src/state/actions.ts
import { graph } from './graph'
import { count } from './inputs'
export function increment(): void {
graph.commit('increment', tx => {
tx.set(count, graph.read(count) + 1)
})
}
And finally, a throwaway script to prove the pieces fit together. Edit Vite's
generated src/main.tsx down to just this for now:
// src/main.tsx
import { graph } from './state/graph'
import { count } from './state/inputs'
import { doubled } from './state/derived'
import { increment } from './state/actions'
console.log('initial count ', graph.read(count)) // 0
console.log('initial doubled ', graph.read(doubled)) // 0
increment()
increment()
increment()
console.log('count ', graph.read(count)) // 3
console.log('doubled ', graph.read(doubled)) // 6
Run pnpm dev, open the browser console, and you should see exactly
those six lines. If you do — congratulations, the engine is real on your
machine. You have an input, a derivation that recomputed when its
dependency changed, three commits that each advanced time by one, and reads that
returned the post-commit values.
What's next
You have the smallest possible Causl app. It still has one input, one derived, and no UI; in Part 2 we model the incident-tracker domain as a richer graph and introduce the id and namespace rules that scale beyond a single counter.