/** * Hash-chained event log for durable workflow event sourcing. * * Each workflow accumulates an append-only log of `WorkflowLogEntry` records * stored under `ev:{workflowId}:{sequence}`. A separate head record at * `ev:{workflowId}:head` tracks the current sequence counter and the hash of * the most recently committed entry. * * Entries are chained by a `prevHash` field (wyhash of the previous entry's * encoded bytes), enabling tamper detection via {@link EventLog.verify}. * * Callers that hold a batch accumulator (e.g. the engine's checkpoint writer) * pass it to {@link EventLog.append} so that the event write is included in * the same atomic `storage.batch()` call as the checkpoint — the two can never * diverge. * * @module core/event-log */ import type { BatchOperation, Storage } from '../storage/interface.ts'; import { EMPTY_EVENT_HEAD, type EventHeadRecord, type WorkflowLogEntry } from './event-log-shared.ts'; import { type VerifyResult } from './event-log-verify.ts'; import type { WorkflowVersionTuple } from './workflow-version-tuple.ts'; export { EMPTY_EVENT_HEAD }; export type { EventHeadRecord, VerifyResult, WorkflowLogEntry }; /** * Result returned from `appendToBatch()`. Carries the updated head * record AND the entry's wall-clock `timestamp` so post-commit * listeners can emit the exact value written into the durable log * without reaching for `Date.now()` a second time (which could * produce a different value under a ticking `getNow` used in tests). */ export type AppendToBatchResult = { readonly newHead: EventHeadRecord; readonly timestamp: number; }; /** * Append-only, hash-chained event log scoped to a single workflow. * * All reads and writes go through the {@link Storage} interface so the log * works with every backend (memory, SQLite, LMDB, Turso, IndexedDB). */ export declare class EventLog { #private; constructor(storage: Storage, workflowId: string); /** * Synchronously build the batch operations for a new event entry and push * them onto `batchOperations`. * * This is the fast path used by the engine: no storage reads occur. * The caller is responsible for supplying the current `head` (from an * in-memory cache) and storing the returned `newHead` back into that cache * after the batch is committed. * * @returns The updated head record to cache for the next call. */ appendToBatch(event: { type: string; payload: unknown; }, batchOperations: BatchOperation[], head: Readonly, versionTuple?: WorkflowVersionTuple): AppendToBatchResult; /** * Append a new event entry to the log. * * When `batchOperations` is supplied the writes are pushed onto it instead * of being flushed immediately, enabling the caller to include them in the * same atomic `storage.batch()` call as a checkpoint write. * * @returns The new sequence number, the hash of the appended entry, and the * updated head record that the caller should cache for the next append. */ append(event: { type: string; payload: unknown; }, batchOperations?: BatchOperation[], versionTuple?: WorkflowVersionTuple): Promise<{ sequence: number; hash: string; newHead: EventHeadRecord; }>; /** * Iterate over all log entries in ascending sequence order. * * @param options.fromSequence Start at this sequence number (inclusive). Defaults to 0. */ scan(options?: { fromSequence?: number; }): AsyncIterable; /** * Return all entries up to and including `toStep`. * * "Step" here is the `sequence` field. Entries with `sequence > toStep` * are excluded, so callers can reconstruct state as it was at any point. */ replay(toStep: number): Promise; /** * Walk the log and verify hash-chain integrity, tolerating concurrent * compaction and a compaction watermark. Delegates to {@link verifyEventLog}; * see that function for the full watermark-seeding, retry, and corruption * semantics. */ verify(): Promise; /** * Read the current head record from storage and return it. * * This is the async counterpart to the synchronous cache lookup in the * engine. Call it when resuming a workflow after an engine restart so that * the in-memory `#eventLogHeads` cache can be re-seeded before the next * {@link appendToBatch} call. * * Returns `EMPTY_EVENT_HEAD` (sequence -1) when no head record exists * (i.e., the log is empty or this workflow has never written an event). */ loadHead(): Promise; }