Skip to content

State engine

The state engine (@carbon/state) is what makes a Carbon runtime stateful rather than a mock. Every request routes through it, every mutation lands in an in-memory resource store, and every write is appended to a journal so you can walk history without replaying traffic.

A snapshot is the entire state of the runtime — every resource, every counter, every relationship — serialized to a stable JSON shape. They travel with your code.

Terminal window
# Grab the runtime's current state
carbon snapshot save baseline
# Restore it later (same shell, another shell, CI job)
carbon snapshot load baseline
# List and delete
carbon snapshot list
carbon snapshot delete baseline
# Push/pull to the shared control plane (Team+)
carbon snapshot push baseline
carbon snapshot pull baseline

Locally, snapshots live in .carbon/snapshots/*.json. Commit them. CI restores them at the start of the run so every test starts from the same known baseline.

From the SDK:

const snap = await replica.snapshot.save('after-refund');
await replica.snapshot.restore(snap);

Every write goes through an append-only journal. The journal is what lets Carbon do more than snapshot-and-restore:

  • Rewind. Roll state back to any journal position without replaying traffic.
  • Forward. Re-apply journal entries after a rewind to test hypotheticals.
  • Diff. Compare two positions and see which resources moved.

A fixture is a snapshot that boots with the runtime. Point the runtime at a starting fixture and every request begins from that state instead of empty stores.

await carbon.emulate({ from: './spec.yaml', fixture: './fixtures/seed.json' });

Recordings are the traffic-side counterpart to snapshots: instead of freezing state, they freeze the requests that produced state. The proxy in @carbon/proxy captures every exchange (method, URL, request and response bodies, redacted headers) and writes it to projects/{slug}/recordings/{id}.jsonl in the control-plane storage.

Terminal window
# Capture traffic while pointing your app at the proxy
carbon record https://api.example.com --port 8788

The dashboard’s Recordings section reads that storage prefix and surfaces one row per capture — request count, first/last timestamp, upstream origin, and size — computed by parsing each stored recording. Clicking a row expands its exchanges inline; clicking Replay runs each captured request against a URL you supply and shows per-exchange diff results (status mismatch, body shape mismatch, or connectivity error).

Under the hood:

  • GET /v1/projects/:slug/recordings — list recordings.
  • GET /v1/projects/:slug/recordings/:id/exchanges — dump the captured exchanges.
  • POST /v1/projects/:slug/recordings/:id/replay — replay against a target and persist a recording_replays row so the history survives page reloads.

The carbon replay <recording> --target <url> CLI runs the same comparison locally when you don’t want to go through the control plane.

The state engine is reachable over HTTP too, so anything that speaks fetch can drive it:

POST /__carbon/state/snapshot → { snapshot: {...} }
POST /__carbon/state/restore ← { snapshot: {...} }