Skip to main content
Every Smithers run persists to a relational store: runs, nodes, attempts, output rows, render frames, and internal message history. The factory (createSmithers and friends) owns that store and hands back a typed db handle, so you rarely touch this layer directly except to build a custom backend, inspect a store outside a running workflow, or generate tables from Zod schemas yourself.
SmithersDb is also re-exported from @smthrs/db/adapter for callers pinning the subpath; the facade export above is supported.

SmithersDb

The adapter: wraps a Drizzle database handle and exposes every persistence operation the engine needs. The factory constructs one and the scheduler drives it; only build your own for a custom control plane.
BunSQLiteDatabase | PostgresDescriptor
required
The underlying Drizzle handle. SQLite uses Drizzle queries directly; the Postgres/PGlite descriptor routes through the internal SQL message storage.
methods
insertRun, updateRun, getRun, listRuns, heartbeatRun, requestRunCancel, requestRunHijack, claimRunForResume, listStaleRunningRuns, plus ancestry helpers: track a run start-to-terminal and support crash recovery.
methods
insertNode / getNode / listNodes and insertAttempt / updateAttempt / heartbeatAttempt record per-node execution and retries.
methods
upsertOutputRow, deleteOutputRow, getRawNodeOutput, and hasPhysicalTable read and write a workflow’s typed output tables, keyed by runId / nodeId / iteration.
methods
insertFrame, listFrameChainDesc, and reconstructFrameXml persist and replay the delta-encoded UI frame stream (with an in-memory LRU XML cache).
(writeGroup, operation) => RunnableEffect
withTransaction / withTransactionEffect run a write group atomically; read(label, op) / write(label, op) wrap a single operation as a runnable Effect with metrics and SQLite write-retry. SQLite serializes writes per client, Postgres uses real transactions.
(sql, params?) => RunnableEffect<unknown[]>
Read-only escape hatch: accepts only SELECT / WITH / EXPLAIN / VALUES; any DDL or mutation keyword is rejected before execution. For introspection, never writes.
SqlMessageStorage
Agent-message and event-history store behind the adapter, created from the same handle. See ensureSmithersTables.
Most methods return a RunnableEffect: an Effect you can yield* inside an Effect program, or await directly as a promise. Source adapter.js · adapter/SmithersDb.js · Tests db-adapter.test.js · See also How it works, SchemaRegistryEntry

loadOutputs / loadOutputsEffect

Reads every persisted output row for a run, across all of a workflow’s output tables, in one call. Given the factory’s tables map and a RUN_ID, returns a snapshot keyed by both schema name and physical table name; boolean columns are coerced back to JS booleans, and the reserved input table is skipped.
BunSQLiteDatabase | PostgresDescriptor
required
The same handle the adapter wraps: Drizzle for SQLite, a parameterized $1 query for Postgres.
Record<string, Table>
required
Map of output name to its Drizzle table (the tables value from the factory). Entries without a runId column, or the input entry, are skipped.
string
required
The run to read. Placeholder: RUN_ID.
object
An object whose keys are both the schema name and the snake_case table name; each value is the array of Drizzle-shaped rows (camelCase keys) for that run.
loadOutputsEffect is the same query returning an Effect.Effect<OutputSnapshot, SmithersError> instead of a promise. Use it inside an Effect pipeline; use loadOutputs for a one-off read.
Source snapshot.js · Tests db-snapshot-load.test.js · See also db-output-roundtrip.test.js

ensureSmithersTables

Creates the core Smithers tables (internal message and event-history storage the adapter depends on) on a fresh handle if they don’t already exist. Safe to call on every boot.
BunSQLiteDatabase
required
The Drizzle handle to initialize. Runs synchronously for SQLite.
For a Postgres descriptor this synchronous helper is a no-op: Postgres schema setup is async, so the Postgres/PGlite entry points await it before the engine starts. Output tables are created separately by syncZodTableSchema.
Source ensure.js · Tests db-ensure.test.js

Zod -> table helpers

Turns a Zod object schema into a SQLite output table. Each table gets the fixed run_id / node_id / iteration prefix and a composite primary key; field keys are snake-cased and mapped to columns by Zod type (string/enum/literal -> TEXT, z.number() / float -> REAL (fractions preserved), z.int() -> INTEGER, boolean -> INTEGER boolean mode, arrays/objects/unions -> JSON TEXT). Pass opts.isInput for the single-PK input-table shape. Output field names can’t reuse the reserved key columns runId/nodeId/iteration (input reserves only runId); a collision throws INVALID_INPUT at construction.
Table
Builds a Drizzle sqliteTable from the schema; what the factory calls to materialize tables.
string
Emits CREATE TABLE IF NOT EXISTS ...; pass opts.dialect (default "sqlite") for Postgres-compatible column types instead.
void
Creates the table and reconciles a drifted one: runs CREATE TABLE, then ALTER TABLE ADD COLUMN for fields missing from an older table, and records column kinds in _smithers_output_schema_columns. Idempotent. sqlite must be a bun:sqlite database (or compatible .run / .query handle).
Array<{ name, sqliteType, kind }>
User-defined columns from the schema, excluding the fixed prefix. Each entry carries the snake_case name, the SQLite type, and the logical kind (string / number / boolean / json).
Source zodToTable.js · zodToCreateTableSQL.js · Tests zod-to-table-unit.test.js · zod-to-sql.test.js

Utilities

Small building blocks behind the helpers above, exported for the same custom-backend cases.
string
Convert a camelCase field name to snake_case, the column-naming rule used throughout. "createdAtMs" -> "created_at_ms".
ZodType
Strip the optional / nullable / default wrappers off a Zod type to reach its base type, so column mapping sees the underlying kind.
string
Renders a Zod object schema as a pretty-printed JSON example string: field descriptions become string placeholders, numbers 0, booleans false, arrays a one-element sample, and enums use their first value. Handy for prompting an agent with the exact output shape it must return.
Source utils/camelToSnake.js · unwrapZodType.js · zod-to-example.js · Tests camel-to-snake.test.js · unwrap-zod-type.test.js · See also Authoring API, Types reference