Skip to main content
The TypeScript SDK integrates with Rivet actor contexts without adding another trace root or depending on Rivet internals. Your application owns one withTrace() root. The adapter binds the live context outside that root and returns a replay-aware context proxy inside it.
The integration is experimental. Capture a fresh trace after enabling it; traces created by the earlier closure recipe do not contain the context fixture replay needs.

Configure the turn

Install @bitfab/sdk, then bind the trace function key once. Configure a TypeScript transform adapter as well when you want Bitfab to capture first-party calls beneath the root; the Rivet context effects do not depend on that transform.
Call handleRivetTurn(context, input) from the existing Rivet action, run handler, or workflow step. bind() removes the live context from the trace input, binds it to the current async chain, and invokes the exact runWorkbench root. withContext(context, () => runWorkbench(input)) is the equivalent lower-level form. Resolve getContext() inside the traced function. In production its proxy delegates to the live context. The root records a frozen pre-turn snapshot of state, vars, actorId, workflowId, name, key, region, and abort state. Calls to supported effect paths become inherited child spans under that root.

Replay

Register or pass the exact traced root, not the Rivet-bound handler:
During replay no Rivet actor is started. getContext() hydrates the recorded pre-turn state into an isolated in-memory context. Selected effect paths return their recorded output without touching live SQLite or broadcasting an event. State mutations apply to the isolated replay state. mockOnReplay is always explicit and defaults to []. The supported selectors are typed strings matching Rivet’s public context paths:
db.transaction() is orchestration rather than a mock target: replay reruns its callback against a transaction proxy and applies policy to each db.transaction.execute call. Likewise, queue.iter() reruns the loop through individually recorded queue.next calls. This keeps changed application code inside callbacks and loops under test. Rivet workflow primitives are different replay boundaries. A selected step, tryStep, try, loop, join, or race returns the recorded operation output and does not invoke its callback. The trace stores the serializable operation input, not the historical JavaScript closure. A mockOverride resolver still receives the callback from the current replay code in inputs, so an override can inspect it or replace the operation output. If replay reaches an unselected effect, a selected effect has no recorded occurrence, the trace predates context fixtures, or code uses an unsupported live capability, the item fails closed. Bitfab does not guess whether an operation is safe and does not fall back to a live actor.

Trace shape and Rivet recovery

The adapter adds no root and no durable step. One executed turn produces the existing application root, transformed first-party descendants, and one integration span per supported context operation. Rivet remains authoritative for scheduling, retries, checkpointing, and recovery.
  • A retry produces a separate Bitfab trace for each executed attempt.
  • A completed durable step restored from Rivet history invokes no user callback and produces no new trace.
  • Fixed database, KV, queue, schedule, cron, run-wakeup, persistence, broadcast, sleep, and destroy operations are replayable through explicit effect selectors.
  • Every public workflow primitive is an output-mock boundary: step, tryStep, try, loop, sleepUntil, rollbackCheckpoint, join, race, removed, and getVersion. Replay also restores workflowId and abort state and provides a no-op workflow logger.
  • Dynamic actor clients, live connections, requests, runtime sockets, and background lifecycle handles are not returned as fake recorded objects. They fail closed until they have dedicated virtual-handle semantics.
  • The source actor and its SQLite database are never rewound or mutated by replay.
The SDK has no runtime dependency on Rivet and does not call a private Rivet API. It adapts the public context object your application already receives.

Verified scope

Focused SDK tests cover one-root nesting, frozen state fixtures, every actor effect path, all public workflow primitives, callback-skipping output mocks, current-callback access from overrides, transaction callback replay, completable queue messages, iterator expansion, missing-recording rejection, default no-mock behavior, and concurrent actor-context isolation. An occurrence test replays 117 repeated SQL calls and verifies every recorded output stays at the same call index. A real RivetKit 2.3.15 actor-host suite covers SQLite, state, broadcasts, concurrent actors, workflow queues and sleep, and retries with client capture on and off. It also executes 21 non-destructive operations against the real runtime across database transactions, KV, schedules, cron, queues, run wakeups, state persistence, and broadcast. The server-backed example sends that broad case to Bitfab too, then stops Rivet and verifies basic replay, the 117-call SQL case, all safe effect families, and the explicit replay-mocking guard through four separate trace functions. A production-shaped test also runs a workbench actor on RivetKit 2.3.17 with Bitfab’s source transform, shuts the actor down, and successfully replays the captured root. The replay uses recorded outputs for step, loop, removed, and application-owned derived database handles.