import { ReadRow, Uuid, RecordChange, RecordsResult, RecordChangeResult, RecordsError, RestoreResult, RecordsErrorCode } from '@ai-matrx/records'; import { RecordsClient } from '@ai-matrx/records/core'; /** The version a row carries itself, when the host's read gave it one. */ declare function ownVersion(row: ReadRow): number | null; /** THE CONFLICT, on a card, a badge and the floating notice — the store said somebody else won. */ declare const STALE_MOVE_LABEL = "Changed by someone else"; /** THE VERSION COULD NOT BE READ, so nothing was written — never shown as a conflict. */ declare const VERSION_UNREAD_LABEL = "Could not check for changes"; /** The refusal for "the version the person saw is not known". */ declare function versionUnread(): RecordsError; /** Is this refusal "the version could not be read" (never "somebody else changed it")? */ declare function isVersionUnread(error: RecordsError | null | undefined): boolean; /** Is this refusal "somebody else changed it first"? */ declare function isChangedElsewhere(error: RecordsError | null | undefined): boolean; interface RecordVersions { /** The version the person saw this record at; waits for the read of freshly drawn rows. `null` = unread. */ seen: (id: Uuid) => Promise; /** The same, for many records at once. */ seenMany: (ids: readonly Uuid[]) => Promise>; /** A write this screen made moved the record on (a version only goes up). */ wrote: (id: Uuid, version: number) => void; /** * Read these records' versions again now: the person pressed Reload, or the store refused a write * because somebody else won (`PT409`) — the rows are re-read too, so the next write is against what * the person then sees. A change that moved only the version (who sees the row) never re-reads by * content, so without this the row stayed refused until the screen was opened again (verifier V10). */ refresh: (ids: readonly Uuid[]) => void; } /** * THE VERSIONS AT LOAD for the rows a screen drew. Stable across re-renders (one handle); re-reads only * the rows whose content changed. `mayRead` leaves out rows the screen will never write (a viewer's). */ declare function useRecordVersions(client: Pick, rows: readonly ReadRow[], mayRead?: (row: ReadRow) => boolean): RecordVersions; /** One record's version, read now — for a screen that shows something else first (a preview, a proposal). */ declare function readVersionNow(client: Pick, id: Uuid): Promise; /** ONE record's update, against the version the person saw (`add`: words that also become choices). */ declare function updateRecordAt(client: Pick, args: { record_id: Uuid; patch: Record; version: number | null; add?: Record | undefined; }): Promise>; /** One change of a batch: an update NAMES the version it was made against (`null` = unread). */ type VersionedChange = Extract | Extract | { op: "update"; record_id: Uuid; patch: Record; expected_version: number | null; }; /** MANY CHANGES TO ONE TABLE, ONE TRANSACTION. Any update with an unread version refuses the batch. */ declare function changeRecordsAt(client: Pick, args: { table_id: Uuid; changes: readonly VersionedChange[]; }): Promise>; /** A pipeline card to another stage, against the version the board drew it at. */ declare function movePipelineCardAt(client: Pick, args: { record_id: Uuid; to: string; also?: Record | undefined; version: number | null; }): ReturnType; /** Many pipeline cards, one call; every card names its version or the batch is not sent. */ declare function movePipelineCardsAt(client: Pick, args: { moves: ReadonlyArray<{ record_id: Uuid; stage: string; version: number | null; }>; }): ReturnType; /** * PUT A VERSION BACK (one column, or the whole record), only while the record is still at the version * the person saw the preview at. The restore doors take no expected version, so the check is ONE * header read just before — a colleague's change since the preview is refused as a conflict; only a * write landing inside that one round trip can still slip past (reported: needs a store-side version). */ declare function restoreAt(client: Pick, args: { record_id: Uuid; version: number; field_key?: string | null | undefined; seenVersion: number | null; }): Promise>; /** * A TABLE'S OWN VERSION, read now. `custom.record_headers` answers RECORDS only (it never returns a * Table's row: measured on the clone 2026-10-02, 0 rows for a Table id), so a Table's version is the * newest of its revisions (`custom.io_revisions`, which does answer a Table — v10 for Cedar Ridge PT's * Patient Visits). The history can fall behind the record; a write on it goes through * `updateOnTheWinningVersion`, which resends once on the winning version when the column it changes * is still what the screen drew. */ declare function readTableVersionNow(client: Pick, tableId: Uuid): Promise; /** * A TABLE'S OWN RECORD (its name, its row label): the version of the table document this screen drew, * read again whenever that document arrives with new content (never per render). */ declare function useTableVersion(client: Pick, tableId: Uuid | null | undefined, table: unknown): () => Promise; type Answer = { ok: boolean; }; /** * `freshMs` > 0: the answer is kept that long after it was asked. `freshMs` <= 0: shared only while it * is IN FLIGHT — an asker after it settled asks again (for answers that may move at any moment). */ declare function askOnce(client: object, key: string, ask: () => Promise, freshMs: number): Promise; /** Tests only: forget everything kept for every client (tests that share one data source across cases). */ declare function forgetAllAsked(client: object): void; /** Forget every kept answer whose key starts with `prefix` (a write changed what it answers). */ declare function forgetAsked(client: object, prefix: string): void; /** * How long a column's choices are shared between askers: one page open, including the records * client being rebuilt when the active organization resolves (~2 s in) and every effect that re-runs * after it. 2 s ended just before the second fire (lane DEDUPE-READS: Spaces measured one at hydration * and one ~2 s later). A write on this page ends it at once (`structureEpoch`); another person's edit * shows within this window at most. */ declare const FIELD_OPTIONS_FRESH_MS = 10000; /** A column's choices, asked once for every asker in the same moment (`custom.field_options`). */ declare function fieldOptionsOnce(client: Pick, fieldId: string): ReturnType; /** How long a page of badges is shared between askers: the one page open, ended by any explicit refresh. */ declare const ENRICH_CELLS_FRESH_MS = 5000; /** * A refusal in this package's own words. * * @param code what KIND of refusal — picks the notice's title and its fallback remedy. * @param sentence what happened, for a person. * @param remedy what to do now, when this call site knows better than the code's default. * @param diagnostic an engineer's note (a thrown message, a status) — rendered out of sight. */ declare function refusal(code: RecordsErrorCode, sentence: string, remedy?: string | null, diagnostic?: string | null): RecordsError; /** * A refusal made from something THROWN in the browser. The thrown text is an * engineer's (a parser's "Unexpected token } in JSON at position 14", a * library's stack) and goes out of sight; the person reads `sentence`. */ declare function refusalFromThrown(thrown: unknown, sentence: string, remedy?: string | null, code?: RecordsErrorCode): RecordsError; /** * The store's refusal when there is one, otherwise this package's own sentence — * for the call sites whose answer MAY carry an error (`res.error?.message ?? "…"` * was the old shape, and it printed the store's message raw). */ declare function refusalOr(error: RecordsError | null | undefined, code: RecordsErrorCode, sentence: string): RecordsError; /** * Whatever a host callback threw, as a refusal: the store's own `RecordsError` * when that is what arrived (it is drawn with its own words and remedy), and * otherwise `refusalFromThrown` — the thrown text out of sight, `sentence` in view. */ declare function asRefusal(thrown: unknown, sentence: string, remedy?: string | null, code?: RecordsErrorCode): RecordsError; /** Two values the way a person would compare them: empty is empty, a list is its contents. */ declare function sameStoredValue(left: unknown, right: unknown): boolean; declare function updateOnTheWinningVersion(client: Pick, recordId: Uuid, patch: Record, expectedVersion: number | null, /** What this screen started from, per patched column — or `always` to resend regardless. */ base: Record | "always"): Promise>; export { ENRICH_CELLS_FRESH_MS, FIELD_OPTIONS_FRESH_MS, STALE_MOVE_LABEL, VERSION_UNREAD_LABEL, asRefusal, askOnce, changeRecordsAt, fieldOptionsOnce, forgetAllAsked, forgetAsked, isChangedElsewhere, isVersionUnread, movePipelineCardAt, movePipelineCardsAt, ownVersion, readTableVersionNow, readVersionNow, refusal, refusalFromThrown, refusalOr, restoreAt, sameStoredValue, updateOnTheWinningVersion, updateRecordAt, useRecordVersions, useTableVersion, versionUnread }; export type { RecordVersions, VersionedChange };