import type { AuthoringApiClient, UpdateItemInput } from "../api/client.js"; import type { Operation, OperationIr } from "../ir/operations.js"; import type { PlannedAction, PlanSummary } from "./plan.js"; import type { ExecuteOptions } from "./execute-types.js"; /** * Write-through for the push-scoped caches (see * `ExecuteOptions.idSnapshotCache` / `versionStackCache`): merge an * update-write's fields into the cached snapshot, or bump a version * add's stack to the op's declared target. Called from the apply loop * at enqueue/dispatch time — BEFORE the wire call resolves — so plans * of later ops read their predecessors' writes. A write that * subsequently fails either aborts the push (fatal → rollback; the * poisoned cache is never read again) or skips on the * unregistered-language tolerance (later ops on that language fail and * skip identically, so the optimistic cache stays consistent). * * Snapshot merges are COPY-ON-WRITE: `buildAction` attaches the cached * object to each action as its rollback `snapshot`, so mutating it in * place would corrupt the pre-op state rollback restores from. */ export declare const recordPendingWrite: (mutation: PooledMutation, op: Operation, options: Pick) => void; /** Mutations the flush pool may carry. */ export type PooledMutation = { kind: "updateItem"; input: UpdateItemInput; } | { kind: "addItemVersion"; itemId: string; language: string; addCount: number; }; /** One planned pooled write (`updateItem` or `addItemVersion`) queued into the flush pool. */ export interface PooledWrite { index: number; op: Operation; action: PlannedAction & { mutation: PooledMutation; }; } export declare const isPooledMutation: (mutation: NonNullable) => mutation is PooledMutation; /** * Bounded-concurrency flush pool for `updateItem` and `addItemVersion` * mutations — the apply loop's throughput lever (see * `ExecuteOptions.applyConcurrency`). * * Invariants: * - **Per-(item, language) serialization.** Tasks chain on the target * (itemId, language) version stack, so writes to the same stack always * apply in op order — while stacks of DIFFERENT languages on the same * item overlap freely (Sitecore versions each language independently). * This is what lets a component's 9 locale version-adds run * concurrently instead of one at a time. * - **Cell coalescing.** Consecutive queued-but-not-started `updateItem` * entries for the same (itemId, language, version) cell merge into ONE * call with their `fields` concatenated — semantically identical to N * sequential single-field calls (`UpdateItemInput` carries * language/version at the input level, so only same-cell entries may * merge). An `addItemVersion` enqueue CLOSES its stack's pending cells * so later field writes can never merge across the version boundary. * - **Failure isolation.** A failed coalesced call is retried per-entry * sequentially so the failing op is identified; each entry failure * then follows the sequential apply-error semantics — language-skip * tolerance first, otherwise the first failure is recorded as * `fatal` for the main loop to turn into rollback + abort at the * next drain point. * * Plan-time reads coordinate through `settle(itemId, language)`: the main * loop awaits just that stack's chain before planning an op that reads it, * instead of draining the whole pool (see `settleForPlan`). * * The pool never rejects: every task traps its own errors into `fatal`, * and `drain()` resolves once all in-flight work settles. */ export declare class WritePool { private readonly deps; private readonly limit; private active; private readonly waiters; private readonly chains; /** Last VERSION-ADD task per stack — the narrower settle target for plan reads that only care about version existence (see settleAdds). */ private readonly addChains; private readonly pendingCells; private readonly tasks; fatal?: { entry: PooledWrite; message: string; }; constructor(limit: number, deps: { client: AuthoringApiClient; summary: PlanSummary; applied: PlannedAction[]; emit: ExecuteOptions["emit"]; onError: ExecuteOptions["onError"]; }); private acquire; private release; /** Chain `run` onto a stack's task chain, bounded by the semaphore. */ private chainTask; enqueue(entry: PooledWrite): void; private enqueueVersionAdd; private flushCell; private recordFailure; /** * Await ONLY the given (itemId, language) stack's in-flight writes — * the plan-read coordination primitive. Unlike `drain()`, other stacks * keep flowing, so a plan read for item A never stalls behind item B's * writes. Callers check `fatal` afterwards. */ settle(itemId: string, language: string | undefined): Promise; /** * Await only the given stack's in-flight VERSION ADDS — the narrower * settle for plan reads that care about version existence but not * field values (`SetField` drift diffs, `AddItemVersion` * reconciliation). Plain field writes to the same stack keep flowing, * which is what preserves same-cell coalescing: a page's consecutive * SetFields would otherwise each wait for the previous one's POST. */ settleAdds(itemId: string, language: string | undefined): Promise; drain(): Promise; } /** Per-IR refKey → languages its `AddItemVersion` ops target — see `ExecuteOptions.versionStackCache`. */ export declare const indexAddVersionLanguages: (ir: OperationIr) => Map; /** Pool when `applyConcurrency` asks for overlap, undefined for the historical serial apply. */ export declare const maybeCreateWritePool: (options: ExecuteOptions, deps: ConstructorParameters[1]) => WritePool | undefined; /** * Await the pool stacks whose settled state this op's PLAN reads: * * - `SetField` / `AddItemVersion` need their target stack's VERSION * ADDS applied (a versioned diff or reconciliation against a stack * whose add is still in flight plans against stale state) — but NOT * its field writes, which touch different fields by construction; * waiting on those would serialize the very writes the pool exists * to overlap (`settleAdds`). * - `SetBaseTemplates` / `SetStandardValues` / `AppendToMultiList` * diff/merge against SHARED field VALUES — they await the full * (item, undefined-language) stack chain (`settle`). * * RefKeys that aren't captured yet have no pooled writes (pooled inputs * are built FROM captured ids), so they settle nothing. Ops that don't * read pooled state (creates, media, prunes, site ops) settle nothing — * their DISPATCH still global-drains via `applySequential`. */ export declare const settleForPlan: (pool: WritePool, op: Operation, capturedItemIds: ReadonlyMap) => Promise;