Engine
open/openSafe and the Engine class, covering reads, writes, transactions, declared boards, watch, and operational helpers.
import { open, openSafe, Engine } from "lakefront";
One DuckDB instance holding the lake plus every attached board replica, fronted by a connection pool and an LRU of attached files. Every fallible method returns a Result from better-result.
Functions
open
function open(options: OpenOptions): Promise<Engine>;
| Parameter | Type | Default | Description |
|---|---|---|---|
options |
OpenOptions |
(required) | See OpenOptions. |
Returns the opened Engine.
Attaches the lake, starts the WAL and flusher (under writePath: "wal"), and starts the head-cache poller. open throws LakeUnavailable on a boot failure; use openSafe to receive that failure as a Result. It creates the cache directory and, for a local (non-s3://) data, the data directory itself, so there is nothing to mkdir before calling it.
await using engine = await open({
postgres: "postgres://lakefront:lakefront@127.0.0.1:5432/lakefront",
data: "./data",
});
Two required fields. Everything else (where the cache lives, how many rows stay inlined, how the cluster is shaped) has a default. Engine implements AsyncDisposable, so await using engine = await open(...) closes it (connection pool, WAL, flusher) when the scope ends; close() is idempotent.
openSafe
function openSafe(options: OpenOptions): Promise<Result<Engine, LakeUnavailable>>;
Returns a boot failure as a Result instead of throwing.
resetLake
function resetLake(options: Pick<OpenOptions, "postgres" | "data" | "namespace">): Promise<void>;
| Parameter | Type | Default | Description |
|---|---|---|---|
options |
Pick<OpenOptions, "postgres" | "data" | "namespace"> |
(required) | Same shape open() takes, so a reset names exactly the lake the engine would open (namespace defaults to "lakefront" the same way). |
Removes both halves of a lake: drops the DuckLake catalog schema and deletes the data directory.
dropCatalogSchema
function dropCatalogSchema(postgres: string, namespace: string): Promise<void>;
| Parameter | Type | Default | Description |
|---|---|---|---|
postgres |
string |
(required) | Catalog Postgres, URL or keyword/value form. |
namespace |
string |
(required) | The catalog schema to drop. |
Drops the DuckLake catalog schema alone, without deleting the data directory. Safe to call when the schema does not exist. resetLake calls this and then removes data too.
toKeywordDsn
function toKeywordDsn(input: string): string;
Converts a standard Postgres URL or the keyword/value form into the keyword/value DSN DuckLake’s ATTACH accepts. What open() does internally to postgres before use.
pgOptionsFromDsn
function pgOptionsFromDsn(input: string): {
hostname: string;
port: number;
database: string;
username: string;
password: string;
max: number;
};
Converts either input form into the options shape Bun’s SQL client accepts.
messageOf
function messageOf(cause: unknown): string;
Normalizes a thrown value into a message without swallowing its type: cause instanceof Error ? cause.message : String(cause). Also exported from lakefront/contract, where it is defined; re-exported here so engine code keeps one import path.
Engine
Instances come from open/openSafe; there is no public constructor. Engine implements AsyncDisposable.
Reads
query
query(
boardId: string,
sqlTemplate: string,
options?: QueryOptions,
): Promise<Result<QueryOutcome, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | The board to query. |
sqlTemplate |
string |
(required) | SQL using {{table}} for the board’s table. |
options |
QueryOptions |
{} |
See QueryOptions. |
Returns Result<QueryOutcome, EngineError>.
Sync-then-query: the board’s replica catches up to the head (or to minWatermark, if given) before the SQL runs against it. sync runs that catch-up alone, without a query. See the consistency guide for the watermark model and read-your-write semantics.
sync
sync(boardId: string): Promise<Result<SyncResult, EngineError>>
Runs the sync-then-query catch-up for boardId without executing a query. Returns the SyncResult it produced.
watermarkOf
watermarkOf(boardId: string): Promise<Result<number, EngineError>>
The board’s replica watermark, without a sync.
rowCount
rowCount(boardId: string): Promise<Result<number, EngineError>>
Row count on the board’s attached replica.
head
head(): number
The cached head, with no round-trip. headFromCatalog() pays for the authoritative one.
headFromCatalog
headFromCatalog(): Promise<Result<number, EngineError>>
The authoritative head, read from the catalog. One round-trip; head() is the cached answer most callers want.
withReplica
withReplica<T>(
boardId: string,
fn: (ctx: { conn: DuckDBConnection; replica: LocalReplica; lake: Lake; head: HeadCache }) => Promise<T>,
): Promise<Result<T, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | The board whose replica to hand to fn. |
fn |
(ctx) => Promise<T> |
(required) | Callback given a leased connection and the attached replica. lake and head are internal types, not otherwise part of this reference surface. |
Low-level access to an attached replica on a pooled connection. Attaching and busy-tracking are handled; sync is not, so the caller decides.
Writes
All writes return Result<number, EngineError>, where the number is the watermark the write committed at: hand it back as minWatermark on a subsequent query to read your own write.
insert
insert(boardId: string, row: Row, options?: WriteOptions): Promise<Result<number, EngineError>>
Inserts one row. Sugar over insertMany.
insertMany
insertMany(
boardId: string,
rows: readonly Row[],
options?: WriteOptions,
): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to write to. |
rows |
readonly Row[] |
(required) | Rows to insert. |
options |
WriteOptions |
{} |
See WriteOptions. |
Inserts rows as one write.
upsert
upsert(boardId: string, row: Row, options?: WriteOptions): Promise<Result<number, EngineError>>
Upserts one complete row. Sugar over upsertMany.
upsertMany
upsertMany(
boardId: string,
rows: readonly Row[],
options?: WriteOptions,
): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to write to. |
rows |
readonly Row[] |
(required) | Complete row versions, not patches. |
options |
WriteOptions |
{} |
See WriteOptions. |
Append-based upsert: writes a new version of each row instead of issuing an UPDATE. An UPDATE on a DuckLake table costs about 2.5x an INSERT, because it has to locate the existing rows and mark them deleted first; appending skips that. Rows must be complete, and superseded versions accumulate until compact() reclaims them. The winner is chosen by version stamp, so across nodes it is last-writer-wins to whatever precision the clocks agree on, which is why update/updateMany remain the default and this is opt-in.
update
update(
boardId: string,
id: string,
patch: Row,
options?: WriteOptions,
): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to patch. |
id |
string |
(required) | Row id to patch. |
patch |
Row |
(required) | Columns to set. |
options |
WriteOptions |
{} |
See WriteOptions. |
Patches one row by id.
updateMany
updateMany(
boardId: string,
updates: ReadonlyArray<{ readonly id: string; readonly patch: Row }>,
options?: WriteOptions,
): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to patch. |
updates |
ReadonlyArray<{ id: string; patch: Row }> |
(required) | One entry per row to patch. |
options |
WriteOptions |
{} |
See WriteOptions. |
Applies many single-row patches as one atomic write, the batch shape OLTP loops actually produce, without paying a lake commit or a WAL append per row. Entries apply in array order, so two patches to the same id stack exactly as two update() calls would.
delete
delete(boardId: string, id: string, options?: WriteOptions): Promise<Result<number, EngineError>>
Deletes one row by id. Sugar over deleteMany.
deleteMany
deleteMany(
boardId: string,
ids: readonly string[],
options?: WriteOptions,
): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to delete from. |
ids |
readonly string[] |
(required) | Row ids to delete. |
options |
WriteOptions |
{} |
See WriteOptions. |
Deletes rows by id.
compact
compact(boardId: string): Promise<Result<number, EngineError>>
Collapses superseded versions left behind by upsert.
Transactions
transaction
transaction<T>(
boards: readonly string[],
fn: (tx: TransactionOps) => Promise<T>,
options?: WriteOptions,
): Promise<Result<{ value: T; watermark: number }, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boards |
readonly string[] |
(required) | Every board the body touches, declared up front. |
fn |
(tx: TransactionOps) => Promise<T> |
(required) | The transaction body; runs against the handle in TransactionOps. |
options |
WriteOptions |
{} |
See WriteOptions. |
Runs several writes as one atomic commit spanning every board named in boards. The whole block runs on one connection (or as one WAL unit, under writePath: "wal"), and the returned watermark is the read-your-write watermark for everything the block wrote.
const r = await engine.transaction(["accounts", "history"], async (tx) => {
await tx.update("accounts", id, { balance: next });
await tx.insert("history", { id: crypto.randomUUID(), delta });
});
Lifecycle
createBoard
createBoard(boardId: string, options?: CreateBoardOptions): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to create. |
options |
CreateBoardOptions |
{} |
See CreateBoardOptions. |
Fails with BoardExists if the board is already there. It is the explicit counterpart to write-time auto-create, and the only place a board’s columns can be declared rather than inferred:
await engine.createBoard("billing", {
columns: { amount: "double", currency: "text", paid: "boolean", due: "timestamp" },
strict: true,
});
Base columns (id, updated_at, state) exist on every board already and cannot be redeclared, neither can a column under the reserved _ prefix. Declaring a name that collides with either fails with ReservedColumnName.
dropBoard
dropBoard(boardId: string): Promise<Result<number, EngineError>>
Removes the board and every row, everywhere. Other nodes need no message: the drop advances the snapshot log, so their next sync discovers the table gone.
truncateBoard
truncateBoard(boardId: string): Promise<Result<number, EngineError>>
Empties a board, keeping it and its columns.
boards
boards(): Promise<Result<string[], EngineError>>
Every board in the lake, whether or not this node has it cached.
hasBoard
hasBoard(boardId: string): Promise<Result<boolean, EngineError>>
Whether boardId exists in the lake.
addColumn
addColumn(boardId: string, column: string, type: ColumnType): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to add the column to. |
column |
string |
(required) | Column name. |
type |
ColumnType |
(required) | See ColumnType. |
Declares a column on a board, without waiting for data to imply it. Required under columns: "declared" (engine-wide) or a board created with strict: true, where inference is off. Useful under inference too: it fixes a column’s type up front instead of letting the first value decide.
widenColumn
widenColumn(boardId: string, column: string, to: WidenTarget): Promise<Result<number, EngineError>>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to widen a column on. |
column |
string |
(required) | Column name. |
to |
WidenTarget |
(required) | Target type, see WidenTarget. |
Widens one column’s type, deliberately and irreversibly. Column types are inferred once, when a write first mentions the column, and fixed after that: a later value that does not fit is refused with ColumnTypeConflict rather than silently reshaping the board. This is the escape hatch, and it only ever widens: a request that would narrow a column is refused, so nothing already written can stop fitting.
await engine.widenColumn("acme_roadmap", "priority", "VARCHAR");
Feed
watch
watch(
boardId: string,
options?: { after?: number; signal?: AbortSignal; pollMs?: number },
): AsyncGenerator<ChangesPage, void, undefined>
| Parameter | Type | Default | Description |
|---|---|---|---|
boardId |
string |
(required) | Board to follow. |
options.after |
number |
current head | Watermark to start from; a past value catches up first. |
options.signal |
AbortSignal |
undefined |
Ends the loop when aborted. |
options.pollMs |
number |
500 |
Poll fallback interval for writes on other nodes. |
Follows a board’s change feed as an async iterable: catch-up from after, then live.
for await (const event of engine.watch("tasks", { after })) {
if (event.resync) {
// re-read current state, then continue -- the iterator already
// moved its cursor to event.from
} else {
// event.records
}
}
Each yielded event is a non-empty ChangesPage. Local writes wake the loop immediately; writes landing on other nodes are picked up by the poll fallback, so the feed is complete either way, and the wakeup only sharpens latency. resync: true is yielded as an ordinary value and the iterator continues from the new floor; it is not an error. A read failure is thrown (the same EngineError changes() would return as a Result). Design, retention, and what resync means for a consumer are covered in the watch guide.
changes
changes(boardId: string, options: { after: number; limit?: number }): Promise<Result<ChangesPage, EngineError>>
One page of the change feed for a board above a watermark this engine handed out. watch is a loop built on top of it, and is the surface most callers want directly.
onChange
onChange(listener: (event: ChangeEvent) => void): () => void
Registers a local trigger, called once per touched board after each of this node’s own write acks; fires on a microtask, so a slow or throwing listener can neither delay nor fail the write. It sees only this node’s own writes (that local signal is what wakes watch between polls) and is not itself a substitute for watch or changes. Returns the unsubscribe function.
Operations
maintain
maintain(olderThanHours?: number): Promise<Result<void, EngineError>>
Runs lake maintenance now (snapshot expiry, file cleanup, small-file merge); under writePath: "wal" the flusher also runs it hourly.
flush
flush(boardId?: string): Promise<Result<FlushStats[], EngineError>>
Drains the WAL into the lake immediately: one board, or every board with pending records when boardId is omitted. A no-op under writePath: "lake".
evict
evict(boardId: string, deleteFile?: boolean): Promise<void>
Drops a replica from the cache. The file stays on disk unless deleteFile is set, which simulates losing the node’s storage entirely.
stats
stats(): EngineStats
Returns the EngineStats snapshot.
cachedBoards
cachedBoards(): readonly string[]
Boards currently attached in this node’s replica cache.
close
close(): Promise<void>
Closes the connection pool, WAL, and flusher. Idempotent: a second call returns immediately rather than deadlocking on connections the first close already released. await using engine = await open(...) calls this at scope exit via Symbol.asyncDispose.
Types
OpenOptions
What open() and openSafe() take: two required fields (where the catalog Postgres is, where the data lives) and defaults for everything else.
| Option | Type | Default | What it does |
|---|---|---|---|
postgres |
string |
(required) | The catalog Postgres: a standard URL (postgres://user:pass@host/db) or the keyword/value form (postgres:dbname=... host=...). This one database holds the DuckLake catalog, the WAL, and, via inlining, recent row data itself. |
data |
string |
(required) | Where Parquet data files land: a local directory, or an s3:// prefix (with s3 set). Small mutations stay inlined in the catalog and never reach here; see inliningRowLimit. |
s3 |
S3Config |
undefined |
Object storage credentials, required when data is an s3:// prefix. |
namespace |
string |
"lakefront" |
Namespaces this lake’s catalog tables inside Postgres, so independent lakes can share one database. Set it only to run more than one lake against the same Postgres. |
cache |
string |
<data>/.cache (local) or .lakefront/cache (s3) |
Directory for this process’s replica cache files. Per-process soft state; two engines must not share one. |
inliningRowLimit |
number |
10_000 |
Rows below this stay inlined in the catalog instead of becoming Parquet. Inlined rows are the speed layer; DuckLake’s own default of 10 sprays tiny files. |
cacheCapacity |
number |
256 |
Max attached replica files. Locally this exists so eviction is exercised rather than theoretical; a production node holds far more. |
poolSize |
number |
24 |
DuckDB connection pool size. |
headRefreshMs |
number |
5 |
How often the lake head refreshes in the background. Bounds how stale a cross-board read can be; read-your-write is unaffected. |
topology |
"single-node" | "clustered" |
"clustered" |
"clustered" polls the head in the background. "single-node" asserts this process is the only writer, so no polling. See below. |
duckdbThreads |
number |
1 |
DuckDB threads per query. A 1k-row group-by measured 3.2x faster on one thread than across 14: parallelizing one small query wastes cores better spent on concurrent queries. Raise it for boards near 10^6 rows, where the crossover flips. |
replicaWalWrites |
boolean |
false |
Enables DuckDB’s own WAL on replica files. Off by default because the file is a disposable cache and a second write-ahead log is write amplification; costs commit durability across a crash. |
indexColumns |
readonly string[] |
["id"] |
Columns kept ART-indexed on every replica, for point lookups. Add only columns actually hit by point predicates: every entry slows sync apply (about 1.7x measured with two indexes) and costs memory. |
autoCreate |
"on-write" | "never" |
"on-write" |
Whether a write to an unknown board creates it. "never" makes createBoard the only way a board comes into existence; reads never create a board either way. |
columns |
"infer" | "declared" |
"infer" |
"infer" types a column from the first data that names it. "declared" refuses an unknown column with UnknownColumn instead, engine-wide. See below. |
writePath |
"wal" | "lake" |
"wal" |
"wal" acks at a group-committed WAL table in the catalog Postgres and flushes to the lake off-path. "lake" commits every write to DuckLake synchronously. See below. |
flushIntervalMs |
number |
30_000 |
How often the WAL flusher drains into the lake, under writePath: "wal". 0 disables the timer, leaving flush() calls in charge (what tests use). |
groupCommit |
boolean |
true |
Batches concurrent writes into shared DuckLake commits. A commit costs about 7.5ms almost regardless of payload and is serialized catalog-wide, so amortizing it is the only thing that raises write throughput. |
A few of these carry rationale too long for a cell:
topology:"clustered"cross-board reads are stale by at most one interval, bounded by the head-refresh poll."single-node"trades that staleness for exact cross-board reads. Running two"single-node"engines against one lake produces incorrect reads.columns:"infer"is what makes a board schemaless. A single board can opt into declared-only columns on its own viacreateBoard(id, { strict: true }), independent of this engine-wide setting.writePath:"lake"is simpler, but caps throughput at double-digit tps per node."wal"trades that ceiling for the durability contract described under write paths.
S3Config
| Field | Type | Default | What it does |
|---|---|---|---|
endpoint |
string |
(required) | S3-compatible endpoint. |
keyId |
string |
(required) | Access key id. |
secret |
string |
(required) | Secret key. |
ssl |
boolean |
undefined |
Whether to use TLS to the endpoint. |
region |
string |
undefined |
Bucket region. |
QueryOptions
| Option | Type | Default | What it does |
|---|---|---|---|
minWatermark |
number | undefined |
undefined |
Read-your-write: refuses to serve a replica older than this watermark, forcing the head forward if needed. |
bindings |
ReadonlyMap<string, string> | undefined |
undefined |
Named {{token}} bindings beyond {{table}}. |
params |
readonly Cell[] | undefined |
undefined |
Positional bind values ($1, $2, …). |
QueryOutcome
| Field | Type | What it is |
|---|---|---|
result |
QueryResult |
{ columns: string[], rows: Cell[][] }: columnar, not row objects. |
sync |
SyncResult |
What the preceding sync did; see SyncResult. |
cache |
"hit" | "rebuild" | "reattach" |
Whether the replica was already attached, rebuilt from the lake, or reattached from an existing file. |
syncMs |
number |
Time spent syncing, split from query time so a caller can tell freshness cost from execution cost. |
queryMs |
number |
Time spent executing the query itself. |
watermark |
number |
The replica’s watermark after this query: pass back as minWatermark to read this exact write. |
SyncResult
| Field | Type | What it is |
|---|---|---|
from |
number |
Watermark the sync started from. |
to |
number |
Watermark the sync landed at. |
skipped |
boolean |
true when the replica was already at the head and nothing ran. |
changesApplied |
number |
Change records applied. |
rowsInserted |
number |
Rows inserted by this sync. |
rowsDeleted |
number |
Rows deleted by this sync. |
columnsAdded |
string[] |
Columns the sync added to the replica’s schema. |
WriteOptions
| Option | Type | Default | What it does |
|---|---|---|---|
idempotencyKey |
string |
undefined |
Makes the write safely retryable: a second attempt with the same key applies nothing and returns the original watermark. Requires writePath: "wal"; keys are retained for a bounded window (expired by maintenance after 24h). See the idempotency guide. |
actor |
string |
undefined |
Audit identity recorded on the WAL records this write produces. Recorded only under writePath: "wal". |
TransactionOps
The handle transaction’s body runs against. Methods here throw instead of returning Result: a failure aborts the whole transaction rather than being handled statement by statement, and Engine.transaction converts the throw back into the outer Result at the boundary.
| Method | Signature | Notes |
|---|---|---|
insert |
(boardId, row: Row) => Promise<void> |
Sugar over insertMany. |
insertMany |
(boardId, rows: readonly Row[]) => Promise<void> |
|
upsert |
(boardId, row: Row) => Promise<void> |
Sugar over upsertMany. See Engine.upsert. |
upsertMany |
(boardId, rows: readonly Row[]) => Promise<void> |
Append-based upsert inside the transaction; see Engine.upsertMany. |
update |
(boardId, id: string, patch: Row) => Promise<void> |
|
updateMany |
(boardId, updates: ReadonlyArray<{ id: string; patch: Row }>) => Promise<void> |
|
delete |
(boardId, id: string) => Promise<void> |
Sugar over deleteMany. |
deleteMany |
(boardId, ids: readonly string[]) => Promise<void> |
|
scalar |
(boardId, sql: string, params?: readonly Cell[]) => Promise<Cell> |
One value from arbitrary SQL. Sees this transaction’s own writes on writePath: "lake"; under "wal" it sees only committed state. Use get for a read that must observe the transaction’s own writes. |
get |
(boardId, id: string) => Promise<Row | null> |
One row by primary key. Sees this transaction’s own writes under both write paths: the "wal" scope answers it from an in-memory overlay of the transaction’s pending writes. |
CreateBoardOptions
| Field | Type | Default | What it does |
|---|---|---|---|
columns |
Readonly<Record<string, ColumnType>> |
{} |
Columns declared up front, name to type. Typed before any data arrives, so the first value written never decides the type. |
strict |
boolean |
false |
When true, this board refuses writes that name an undeclared column (UnknownColumn), on every node, regardless of the engine-wide columns option. Stored in the catalog and read with a short-TTL cache, so the policy travels with the board. |
ColumnType
The public column-type vocabulary uses plain lowercase names, mapped onto the DuckDB type underneath:
ColumnType |
Underlying SQL type | Notes |
|---|---|---|
"boolean" |
BOOLEAN |
|
"tinyint" |
TINYINT |
|
"smallint" |
SMALLINT |
|
"integer" |
INTEGER |
|
"bigint" |
BIGINT |
|
"hugeint" |
HUGEINT |
|
"float" |
FLOAT |
|
"double" |
DOUBLE |
|
"text" |
VARCHAR |
|
"timestamp" |
TIMESTAMP |
Declarable, but opaque to inference: outside the widening lattice, so it cannot be widened into or out of. |
COLUMN_TYPES is the object above, exported as the runtime source of truth; ColumnType is keyof typeof COLUMN_TYPES. See the schema guide for how inference and widening behave in practice.
WidenTarget
Exclude<ColumnType, "timestamp">: every widenColumn destination except "timestamp", since that type sits outside the lattice a column’s inferred type moves up.
EngineStats
| Field | Type | What it is |
|---|---|---|
flusherParked |
ParkedBoardStat[] |
Boards the periodic flusher has given up on for now ({ boardId, failures, since }); empty under writePath: "lake". |
cached |
number |
Replica files currently attached. |
capacity |
number |
cacheCapacity this engine opened with. |
hits |
number |
Replica-cache hits. |
rebuilds |
number |
Replicas rebuilt from the lake. |
reattaches |
number |
Replicas reattached from an existing file. |
evictions |
number |
Replicas evicted from the cache. |
connectionsInUse |
number |
Pooled connections currently leased. |
ChangeEvent
{ boardId: string, watermark: number, ops: readonly WalRecordOp[] }. Fired to onChange listeners after a write’s ack, once per touched board.
WalRecordOp
{ kind: WalKind, payload: WalPayload }. A change record’s operational core.
ChangesPage
Discriminated on resync: { resync: false, records: ChangeRecord[], next: number } | { resync: true, from: number }. resync: true means the requested range is beyond retention; re-read current state and continue from from.
FlushStats
interface FlushStats {
readonly boardId: string;
readonly from: number;
readonly to: number;
readonly records: number;
readonly skipped: boolean;
}
What one flush() call did for one board.
ParkedBoard
interface ParkedBoard {
readonly boardId: string;
readonly failures: number;
readonly since: number;
readonly retryAt: number;
readonly message: string;
}
A board the periodic flusher has given up on for now: its WAL records are untouched (durability never depended on flushing), but they accumulate until the cause is repaired. stats() itself returns the narrower wire shape ParkedBoardStat ({ boardId, failures, since }); ParkedBoard is the richer local shape, with the retry backoff and message, that the flusher tracks internally.
WalKind
"insert" | "upsert" | "update" | "delete" | "drop".
WalOp
{ boardId: string, kind: WalKind, payload: WalPayload }.
WalPayload
A union keyed by shape: { rows: Row[] } (insert/upsert), { id: string, patch: Row } (update), { ids: string[] } (delete), or { drop: true } (a whole-board tombstone).
Row
Readonly<Record<string, Cell>>, defined once in lakefront/contract and re-exported here.
Cell
string | number | boolean | null, defined once in lakefront/contract and re-exported here.
QueryResult
{ columns: string[], rows: Cell[][] }, defined once in lakefront/contract and re-exported here.
EngineError
The union of every tagged error class the engine can raise: InvalidBoardId, EntityNotAllowed, PlaceholderUnbound, LakeUnavailable, QueryFailed, SyncFailed, ReplicaUnavailable, WriteFailed, ReservedColumnName, ColumnTypeConflict, BoardNotFound, BoardExists, UnknownColumn, each also exported by name. Full catalog, fields, and when each is raised: the error catalog.
Constants
COLUMN_TYPES
The runtime source of truth for the ColumnType table above: { boolean: "BOOLEAN", tinyint: "TINYINT", ..., text: "VARCHAR", timestamp: "TIMESTAMP" }.
BASE_COLUMNS
BASE_COLUMNS = { id: "VARCHAR", updated_at: "TIMESTAMP", state: "VARCHAR" };
The fixed columns every board table has; their types are fixed by the system and never inferred from data.
RESERVED_COLUMN_PREFIX
RESERVED_COLUMN_PREFIX = "_";
The namespace user columns may not write into: a column named _v decides upsert ordering, compaction, and version-scoped deletes.
ENGINE_ERRORS
The lookup object every derivation in the system uses: keyed by tag, values are the error classes themselves. WIRE_TAGS, the engine’s own error-wrapping, and the reference catalog are all built from it, so a class added here is everywhere at once. See the error catalog.