kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
71 lines • 4.7 kB
TypeScript
/**
* # bus/read — the crash-tolerant, schema-validating bus reader (RUNTIME §1, §8)
*
* `readBus` turns bus text (or a bus file) into a stream of typed {@link BusEvent}s. It is
* **crash-tolerant** exactly where a single-writer append-only log can tear: the very last
* line, if the process died mid-write, is an incomplete JSON record with no trailing
* newline — that torn final line is **dropped silently**. Any OTHER unparseable line is
* **mid-file corruption**: it throws loudly with the 1-based line number (RUNTIME §1). A
* `META` header with an unrecognized `bus_schema` is refused loudly (fail-closed, §8).
*
* The distinction is precise: a well-formed writer always ends a record with `\n`
* ({@link ../bus/write.ts serializeEvent}), so a file that ends WITHOUT a newline has a
* partially-written tail. Only that tail may be dropped; an interior blank/garbage line
* means the log itself is damaged and the reader must not paper over it.
*
* Two **forward-compatible** tolerances keep an older build reading a newer bus without crashing
* (fail-closed to a logged skip, never a false success, §8): (1) the META header may carry any
* schema in {@link SUPPORTED_BUS_SCHEMAS} — a v1 bus (no JOURNAL) reads clean under a v2 reader;
* (2) a well-formed {@link JournalEvent JOURNAL} with an unrecognized `kind` is **skipped with a
* logged reason** ({@link BusReadOptions.onSkip}) rather than thrown — its `seq` is still consumed
* so the log stays gap-free. Neither tolerance touches the engine: JOURNAL is author metadata.
*
* Validation is **fail-closed and complete** (RUNTIME §1), in two layers. Per-record
* ({@link validate}): finite `seq`/`ts`, a legal `stream`, and a `type` that actually pairs
* with that stream on the discriminated union (`isValidStreamType` — a `TICK` is `SPOT | BOOK
* | HEARTBEAT`, an `ORDER` a `place | cancel | fill | reject`, …); META always carries the
* supported `bus_schema`. Cross-record ({@link readBusText}): a well-formed bus opens with
* **exactly one META** header (first event, no duplicate) and `seq` is **strictly monotonic,
* gap-free from 0**. Every violation is a loud, line-located {@link BusReadError} — the reader
* never widens a malformed record onto {@link BusEvent} by an unchecked cast.
*/
import type { BusEvent } from "./types.ts";
/**
* A sink for a crash-tolerant SKIP: a record that is well-formed but **forward-compatible-
* unknown** — today only a {@link JournalEvent JOURNAL} whose `kind` this build does not
* recognize. The record is dropped from the yielded stream and its reason logged; it is never
* thrown (fail-closed to a logged skip, RUNTIME §8) and its `seq` is still consumed so the log
* stays gap-free. Defaults to a `console.warn` when the caller supplies none.
*/
export type BusSkipSink = (reason: string, line: number) => void;
/** Options for the bus reader. */
export interface BusReadOptions {
/** Where a crash-tolerant SKIP is reported (see {@link BusSkipSink}). */
readonly onSkip?: BusSkipSink;
}
/** A loud, located bus corruption error (mid-file damage or a schema violation). */
export declare class BusReadError extends Error {
readonly line: number;
constructor(message: string, line: number);
}
/**
* Read typed events from bus **text** (already-loaded JSONL). Torn final line dropped silently;
* interior corruption throws loudly with the line number.
*
* Beyond per-record validation ({@link validate}), this loop enforces the **cross-record**
* well-formedness a single record cannot see (RUNTIME §1): a well-formed bus opens with
* **exactly one META** — the header must be the **first** event and no second META may appear —
* and `seq` is **strictly monotonic, gap-free from 0** (`0, 1, 2, …`). A backward, skipped, or
* teleported `seq`, a missing/late header, or a duplicate header is mid-file corruption and
* raises loudly. (A torn final line is still dropped first — it never reaches these checks.)
*/
export declare function readBusText(text: string, opts?: BusReadOptions): Generator<BusEvent>;
/** Read typed events from a bus **file** (synchronous, deterministic — no wall clock). */
export declare function readBusFile(path: string, opts?: BusReadOptions): Generator<BusEvent>;
/**
* Read typed events from a bus given **either a path or raw JSONL content** (RUNTIME §1).
* Content is detected structurally (a JSONL bus begins with `{`); anything else is read as a
* filesystem path. Crash-tolerant and schema-validating per {@link readBusText}.
*/
export declare function readBus(source: string, opts?: BusReadOptions): Generator<BusEvent>;
//# sourceMappingURL=read.d.ts.map