import { type DbExec } from "../db/client.js"; import { type BoundedTaskRunner } from "./bounded-tasks.js"; import { type LiveCopyCatchUp } from "./live-copy-tasks.js"; import { type YDocStamp } from "./storage.js"; export declare const LIVE_COPY_DEFAULT_FIELD = "default"; export type LiveCopyErrorCode = "not_live_copy" | "not_seeded" | "write_refused" | "read_refused" | "malformed_state" | "unsupported_content" | "state_too_large" | "derive_refused" | "derive_failed" | "build_refused" | "build_failed" | "version_conflict"; export declare class LiveCopyError extends Error { readonly code: LiveCopyErrorCode; readonly docId: string; /** * Refusal reason from the task, the malformed-state reason, the bounded * task error code, or the refused path, depending on `code`. */ readonly reason?: string; constructor(code: LiveCopyErrorCode, docId: string, message: string, options?: { reason?: string; cause?: unknown; }); } export interface LiveCopyCommit { readonly docId: string; readonly origin: "update" | "mutation" | "seed"; /** Null when this commit creates the `_collab_docs` row. */ readonly previousVersion: number | null; readonly version: number; readonly text: string; readonly state: Uint8Array; readonly requestSource?: string; } /** * What `persist` reports. Returning nothing means the app's rows were * derived; `derived: false` means it left them alone (say the document went * back to its legacy writer), and the Yjs state still commits. */ export type LiveCopyPersistOutcome = { derived: true; } | { derived: false; reason: string; }; /** * How a write left the app's derived rows. `unchanged` means nothing was * committed, so persist did not run. */ export interface LiveCopyOutcome { liveCopy: "derived" | "not_derived" | "unchanged"; liveCopyReason?: string; } export interface LiveCopyOptions { /** * Stable identity. Defining a binding under the same name again, as a dev * server does when it evaluates the app's module again, replaces the old * one. */ name: string; /** Which collab documents are live copies. Checked on every write. */ appliesTo(docId: string): boolean | Promise; /** * Hosts the live-copy tasks: its module exports * `tasks = defineLiveCopyTasks({ derive, seed, edits })`. */ runner: BoundedTaskRunner; /** The one root type a live copy may hold. Default "default". */ field?: string; /** Encoded state above this is refused. Default 4 MiB. */ maxStateBytes?: number; /** * Writes to one document allowed to wait while another commits. Beyond * this, a write fails with `build_failed` / `queue_full` instead of * holding its payload until its turn. Default 16. */ maxWaitingWrites?: number; /** * Writes the app's derived stores inside the commit transaction. It runs * before core updates `_collab_docs`, so app row locks are always taken * first. A throw rolls the commit back and reaches the caller unchanged. * It may run more than once per write when a version conflict retries. * It runs in the caller's async context, and `getDbExec()` inside it is * `tx`; a Drizzle `getDb()` handle is not, so query through `tx`. */ persist?(tx: DbExec, commit: LiveCopyCommit): Promise; } export interface LiveCopyBinding { readonly name: string; readonly appliesTo: (docId: string) => boolean | Promise; readonly runner: BoundedTaskRunner; readonly field: string; readonly maxStateBytes: number; readonly maxWaitingWrites: number; readonly persist?: (tx: DbExec, commit: LiveCopyCommit) => Promise; } /** * Declares which collab documents are live copies and how their text is * derived. While a binding exists, core's other write paths (text, JSON, * search-replace, prepared mutations, and the text/JSON/XML seeders) refuse * documents it covers. */ export declare function defineLiveCopy(options: LiveCopyOptions): LiveCopyBinding; /** Synchronous, so a caller can keep main's behavior when none is defined. */ export declare function liveCopiesDefined(): boolean; /** * Removes a binding so its documents take ordinary collab writes again. A * binding already replaced under its name leaves the replacement in place. */ export declare function releaseLiveCopy(binding: LiveCopyBinding): void; export declare function liveCopyBindingFor(docId: string): Promise; /** * Throws `write_refused` when a live copy covers `docId`. With no binding * defined it returns synchronously, so a write that has not taken its lock * yet keeps its place in line. */ export declare function refuseLiveCopyWrite(docId: string, path: string): Promise | undefined; export declare function liveCopyWriteRefused(docId: string, path: string): LiveCopyError; export declare function assertLiveCopy(binding: LiveCopyBinding, docId: string): Promise; /** Throws unless `binding` is still the one registered under its name. */ export declare function assertActiveLiveCopy(binding: LiveCopyBinding, docId: string): void; /** * A commit updates this instance's cache and notifies connected editors as * soon as it lands, so it must own its transaction: inside a caller's, a * later rollback would leave editors holding state that was never saved. */ export declare function assertOwnTransaction(docId: string, path: string): void; export type StoredLiveCopy = { kind: "absent"; } | { kind: "stored"; state: Uint8Array; version: number; digest: string; }; /** * The stored bytes, read strictly: unreadable base64 or a bad version throws * `malformed_state` instead of reading as empty. The bytes are not decoded * as Yjs here; the tasks do that in the runner. */ export declare function readStoredLiveCopy(binding: LiveCopyBinding, client: DbExec, docId: string): Promise; export interface LiveCopyChange { kind: "changed"; state: Uint8Array; text: string; /** What this change added to the state it started from. */ update: Uint8Array; catchUp: LiveCopyCatchUp; } /** Validates stored state in the runner; derives its text when asked. */ export declare function readLiveCopyState(binding: LiveCopyBinding, docId: string, state: Uint8Array, derive: boolean): Promise<{ kind: "unseeded"; catchUp: LiveCopyCatchUp; } | { kind: "live"; text: string | null; catchUp: LiveCopyCatchUp; }>; /** Merges a client update in the runner; null state means absent. */ export declare function applyLiveCopyUpdate(binding: LiveCopyBinding, docId: string, state: Uint8Array | null, update: Uint8Array): Promise<{ kind: "unchanged"; } | { kind: "changed"; state: Uint8Array; text: string; catchUp: LiveCopyCatchUp; }>; /** Seeds an absent or unseeded copy; a live one comes back with its text. */ export declare function seedLiveCopyState(binding: LiveCopyBinding, docId: string, state: Uint8Array | null, source: string): Promise<{ kind: "live"; text: string; } | LiveCopyChange>; /** Runs a named edit from the runner's module on seeded state. */ export declare function editLiveCopyState(binding: LiveCopyBinding, docId: string, state: Uint8Array, edit: string, editInput: unknown): Promise<{ kind: "unchanged"; text: string; } | LiveCopyChange>; /** A caller's own write, added to the transaction of one live-copy write. */ export type LiveCopyCallPersist = (tx: DbExec) => Promise; export declare function sameStamp(a: YDocStamp | null, b: YDocStamp | null): boolean; /** * Commits a derived state: the binding's persist hook, the caller's, then * the `_collab_docs` write, in one transaction, at the row `base` names * (null: no row). Returns null when another writer changed the row first; * nothing is written in that case. */ export declare function commitLiveCopy(binding: LiveCopyBinding, commit: LiveCopyCommit, base: YDocStamp | null, callPersist?: LiveCopyCallPersist): Promise; /** * Runs a caller's persist by itself, for a write that committed nothing, * while the copy is still the row `base` names. Returns false, with nothing * written, when another writer changed it first. */ export declare function persistLiveCopyCall(binding: LiveCopyBinding, docId: string, base: YDocStamp, callPersist: LiveCopyCallPersist): Promise;