import type { SessionRecord } from '../types/session/index.js'; /** * A derived value maintained incrementally from the turn's event log. * * Everything derived from a turn was computed by scanning what was in hand * at the moment somebody asked — `deriveTurnStatus` takes a status and a * park and answers about that instant. That works while the whole turn fits * in memory and stops working the moment it does not: a caller wanting the * status of a turn whose history has been compacted, or of a turn in another * process, has to load the log and fold it, and every caller folds it * slightly differently. * * A read model is that fold, written once and advanced one event at a time. * The registry below is what makes "advanced one event at a time" a * property rather than a hope: it refuses a duplicate and refuses a gap, so * a projection is either correct or absent, never quietly built on a log it * did not fully see. */ export interface ReadModel { /** Stable, and how a caller asks for this projection's state. */ readonly id: string; /** The value before any event. Called once per registry. */ initial(): TState; /** * PURE. Same state plus same event must give the same result, every * time and in any process. * * Returning the SAME object when an event changes nothing is expected * and cheap; the registry does not compare, and a model that allocated * a fresh state per event would still be correct, only wasteful. */ apply(state: TState, event: SessionRecord): TState; } /** An event the registry has already folded in. */ export declare class DuplicateEventError extends Error { readonly details: { seq: number; lastSeq: number; }; constructor(details: { seq: number; lastSeq: number; }); } /** An event that skips one the registry never saw. */ export declare class EventGapError extends Error { readonly details: { seq: number; expected: number; }; constructor(details: { seq: number; expected: number; }); } /** A projection nobody registered. */ export declare class UnknownReadModelError extends Error { readonly details: { id: string; }; constructor(details: { id: string; }); } /** Two models claiming one id. */ export declare class ReadModelCollisionError extends Error { readonly details: { id: string; }; constructor(details: { id: string; }); } /** * Every projection of one turn, advanced together. * * Together, and that is the design: a registry per turn rather than per * model, so `lastSeq` is one number and a caller reading two projections * cannot be handed states derived from different prefixes of the same log. */ export declare class ReadModelRegistry { private readonly models; private readonly states; private seq; /** The seq this registry has folded up to. `0` before any event. */ get lastSeq(): number; register(model: ReadModel): void; /** * Fold one event into every projection. * * REFUSES a duplicate and REFUSES a gap. Both are silent corruptions * otherwise: a duplicate double-counts anything a model accumulates, and * a gap produces a state that looks complete and describes a log the * registry never saw. A caller that legitimately has to skip ahead * rebuilds with {@link replay} instead, which is honest about starting * over. */ apply(event: SessionRecord): void; /** * Throw away every state and fold the whole log again. * * The honest alternative to accepting a gap. A caller that has lost its * place, or that just registered a model into a running registry, gets a * correct answer by paying for the whole log rather than a plausible one * by pretending it did not miss anything. */ replay(events: readonly SessionRecord[]): void; /** The projection's state, or throw for an id nobody registered. */ get(id: string): TState; has(id: string): boolean; } //# sourceMappingURL=registry.d.ts.map