import type { JsonValue } from "../json.ts"; import { copyJson } from "../json.ts"; import { applyImmutableTrusted } from "./apply-immutable-trusted.ts"; import type { Draft } from "./draft.ts"; import { applyImmutable, type NonEmptyPath, type Op, overlap, type Path, RESERVED_SEGMENTS } from "./index.ts"; export type { Draft, JsonValue, Op }; type Primitive = null | boolean | number | string; type Container = JsonValue[] | Record; type Stored = JsonValue; type StoredRef = Primitive | { index: number }; type Status = "open" | "prepared" | "consumed" | "aborted" | "stale"; type StatusCell = { value: Status }; type InsertSource = { refs: StoredRef[] }; type ParentKind = 0 | 1 | 2; // object, base-array entry, inserted-array entry type BasePiece = { kind: "base"; start: number; length: number; step: 1 | -1 }; type InsertPiece = { kind: "insert"; source: InsertSource; start: number; length: number; step: 1 | -1 }; type Piece = BasePiece | InsertPiece; type PieceNode = { piece: Piece; left: PieceNode | undefined; right: PieceNode | undefined; priority: number; elements: number; }; type PieceLocation = { piece: Piece; logicalStart: number; minimum: number; maximum: number }; type DenseRegion = { start: number; length: number }; type DenseCandidates = { indices: number[]; bits: Uint8Array | undefined; length: number }; type ArrayPlan = { removeRuns: number[]; permutation: number[] | undefined; insertRuns: number[]; }; type ArrayOverlay = { root: PieceNode | undefined; pieces: Piece[] | undefined; baseOverrides: Map | undefined; insertOverrides: Map> | undefined; structural: boolean; generation: number; plan: ArrayPlan | undefined; seed: number; locatedOffset: number; baseLocations: PieceLocation[] | undefined; insertLocations: Map | undefined; }; type OverlayNode = { context: OverlayContext; baseIndex: number; parentIndex: number; parentKind: ParentKind; parentKey: string | number; parentSource?: InsertSource; parentPlacement: boolean; target: object; proxy: object; writeKey?: string; writeValue?: StoredRef; writes?: Map; deleteKey?: string; deletes?: Set; readded?: Set; array?: ArrayOverlay; dirty?: boolean; subtreeDirty?: boolean; preparedPath?: Path; }; type OverlayContext = { owner: object; tracker: WeakRef>; baseRevision: number; status: StatusCell; root: OverlayNode | undefined; bases: Container[] | undefined; stored: Container[] | undefined; dirty: OverlayNode[]; nodes: OverlayNode[]; rawNodes: WeakMap | undefined; ops: Op[] | undefined; replacement: boolean; replacementNoop: boolean; baseValue: object | undefined; overlayReleased: boolean; simpleObjectMaterialization: boolean; registryRef: WeakRef | undefined; }; const NODE = Symbol("chord.delta.overlay.node"); const PREPARED = new WeakMap(); const RELEASED: Record = {}; const ARRAY_MUTATORS = new Set([ "push", "pop", "shift", "unshift", "splice", "reverse", "sort", "fill", "copyWithin", ]); const MAX_DELTA_OPERATIONS = 4_096; const MAX_SIMPLE_OBJECT_NODES = 128; const DATA_DESCRIPTOR: PropertyDescriptor = { value: undefined, writable: true, enumerable: true, configurable: true, }; export interface Prepared { readonly base: T; readonly value: T; readonly ops: readonly Op[]; readonly baseRevision: number; abort(): void; } export interface Change { readonly state: Draft; prepare(): Prepared; abort(): void; } export interface Tracker { readonly value: T; readonly revision: number; beginChange(): Change; prepareReplace(value: T): Prepared; adopt(prepared: Prepared): void; } class PreparedImpl implements Prepared { readonly #context: OverlayContext; readonly value: T; readonly base: T; readonly ops: readonly Op[]; constructor(context: OverlayContext, value: T, operations: readonly Op[]) { this.#context = context; this.value = value; this.base = context.baseValue as T; this.ops = operations; PREPARED.set(this, context); } get baseRevision(): number { return this.#context.baseRevision; } abort(): void { abortContext(this.#context); } } class ChangeImpl implements Change { #context: OverlayContext | undefined; #preparedStatus: StatusCell | undefined; #settled = false; constructor(context: OverlayContext) { this.#context = context; } get state(): Draft { const state = this.#context?.root?.proxy; if (state === undefined) throw new TypeError("Cannot use a settled overlay"); return state as Draft; } prepare(): Prepared { if (this.#settled) throw new Error("Change has already been settled"); const context = this.#context!; assertWritable(context); context.status.value = "prepared"; try { if (!context.replacement) context.ops = context.dirty.length === 0 ? [] : emitOperations(context); const prepared = materializePrepared(context); this.#preparedStatus = context.status; this.#settled = true; return prepared; } catch (error) { context.status.value = "aborted"; clearContext(context); this.#settled = true; throw error; } finally { this.#context = undefined; } } abort(): void { if (this.#settled) { if (this.#preparedStatus?.value === "prepared") this.#preparedStatus.value = "aborted"; this.#preparedStatus = undefined; return; } this.#settled = true; abortContext(this.#context!); this.#context = undefined; } } class TrackerImpl implements Tracker { readonly #owner = {}; readonly #contexts = new Set>(); readonly #selfRef: WeakRef>; #value: T; #revision = 0; #pruneBudget = 256; constructor(initial: T) { this.#value = initial; this.#selfRef = new WeakRef(this as unknown as TrackerImpl); } get value(): T { return this.#value; } get revision(): number { return this.#revision; } beginChange(): Change { const context = createContext(this.#selfRef, this.#owner, this.#revision, this.#value, false, this.#value); this.#register(context); return new ChangeImpl(context); } prepareReplace(value: T): Prepared { const context = createContext(this.#selfRef, this.#owner, this.#revision, value, true, this.#value); context.status.value = "prepared"; this.#register(context); try { return materializePrepared(context); } catch (error) { context.status.value = "aborted"; clearContext(context); throw error; } } adopt(prepared: Prepared): void { const context = PREPARED.get(prepared as object); if (context?.owner !== this.#owner) throw new Error("Prepared change belongs to a different tracker"); if (context.status.value === "consumed") throw new Error("Prepared change has already been used"); if (context.status.value === "aborted") throw new Error("Prepared change has been aborted"); if (context.status.value === "stale") throw new Error("Prepared change is stale"); if (context.status.value !== "prepared") throw new Error("Prepared change is not ready"); if (context.baseRevision !== this.#revision) { context.status.value = "stale"; clearContext(context); throw new Error("Prepared change is stale"); } if (this.#value !== prepared.base) { context.status.value = "stale"; throw new Error("Prepared change is stale"); } // Materialization happened during prepare. Adoption is an infallible pointer // swap so storage failure can discard the candidate without touching authority. this.#value = prepared.value; context.status.value = "consumed"; this.#revision += 1; this.#invalidate(context); } releaseContext(context: OverlayContext): void { if (context.registryRef !== undefined) this.#contexts.delete(context.registryRef); context.registryRef = undefined; } #register(context: OverlayContext): void { const reference = new WeakRef(context); context.registryRef = reference; this.#contexts.add(reference); this.#pruneBudget -= 1; if (this.#pruneBudget === 0) { this.#prune(); this.#pruneBudget = Math.max(256, this.#contexts.size); } } #prune(): void { for (const reference of this.#contexts) { if (reference.deref() === undefined) this.#contexts.delete(reference); } } #invalidate(winner: OverlayContext): void { for (const reference of this.#contexts) { const context = reference.deref(); if (context === undefined || context === winner) continue; if (context.status.value === "open" || context.status.value === "prepared") context.status.value = "stale"; // Adoption must not walk every node touched by a competing draft. Drop the // context's strong overlay references in O(1); externally retained proxies // still see the stale status and are reclaimed with their holders. releaseOverlayReferences(context); } this.#contexts.clear(); this.#pruneBudget = 256; } replacementBase(context: OverlayContext): T | undefined { return context.baseRevision === this.#revision ? this.#value : undefined; } } /** Take immutable ownership of an alias-free strict-JSON root in O(1). */ export function track(initial: T): Tracker { return new TrackerImpl(initial); } function materializePrepared(context: OverlayContext): PreparedImpl { const operations = ensureOperations(context); const base = context.baseValue as T; // Operation payloads are already detached from the mutable overlay. The // resulting revision and public batch intentionally share those payloads under // the trusted immutable-transfer contract. const value = operations.length === 0 ? base : context.simpleObjectMaterialization ? (cloneNode(context.root!) as T) : materializeOperations(base, operations); const prepared = new PreparedImpl(context, value, operations); if (context.nodes.length > 4_096) releaseOverlayReferences(context); else clearContext(context); return prepared; } function materializeOperations(base: T, operations: readonly Op[]): T { for (const operation of operations) { // Native splice/permutation remains faster for large structural batches. if (operation[0] === "p" || operation[0] === "m") return applyImmutable(base, operations); } return applyImmutableTrusted(base, operations); } function createContext( tracker: WeakRef>, owner: object, baseRevision: number, root: T, replacement: boolean, base: T, ): OverlayContext { const context: OverlayContext = { owner, tracker, baseRevision, status: { value: "open" }, root: undefined, bases: [], stored: [], dirty: [], nodes: [], rawNodes: new WeakMap(), ops: undefined, replacement, replacementNoop: false, baseValue: base, overlayReleased: false, simpleObjectMaterialization: false, registryRef: undefined, }; context.root = createNode(context, root as Container, undefined); return context; } const sharedObjectHandler: ProxyHandler = { deleteProperty(target, property) { return deleteProperty(nodeForTarget(target), property); }, defineProperty(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Defining overlay properties is not supported"); }, get(target, property) { if (property === "then") { const node = (target as Record)[NODE] as OverlayNode | undefined; if (node === undefined || isSettledContext(node.context)) return undefined; } const node = nodeForTarget(target); if (property === NODE) return node; return getProperty(node, property); }, getOwnPropertyDescriptor(target, property) { return getDescriptor(nodeForTarget(target), property); }, getPrototypeOf(target) { const node = nodeForTarget(target); assertReadable(node.context); return Object.getPrototypeOf(nodeBase(node)); }, has(target, property) { return hasProperty(nodeForTarget(target), property); }, isExtensible(target) { assertReadable(nodeForTarget(target).context); return true; }, ownKeys(target) { return ownKeys(nodeForTarget(target)); }, preventExtensions(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Overlays cannot be made non-extensible"); }, set(target, property, value) { return setProperty(nodeForTarget(target), property, value); }, setPrototypeOf(target) { assertWritable(nodeForTarget(target).context); throw new TypeError("Changing an overlay prototype is not supported"); }, }; const sharedArrayHandler: ProxyHandler = sharedObjectHandler; function createNode( context: OverlayContext, base: Container, parent: OverlayNode | undefined, parentKind: ParentKind = 0, parentKey: string | number = "", parentSource: InsertSource | undefined = undefined, parentPlacement = false, ): OverlayNode { const existing = context.rawNodes!.get(base); if (existing !== undefined) return existing; const target: object = Array.isArray(base) ? [] : {}; const baseIndex = context.bases!.length; const parentIndex = parent?.baseIndex ?? -1; context.bases!.push(base); const node: OverlayNode = { context, baseIndex, parentIndex, parentKind, parentKey, parentPlacement, target, proxy: target, }; if (parentSource !== undefined) node.parentSource = parentSource; (target as Record)[NODE] = node; node.proxy = new Proxy(target, Array.isArray(base) ? sharedArrayHandler : sharedObjectHandler); context.rawNodes!.set(base, node); context.nodes.push(node); return node; } function nodeBase(node: OverlayNode): Container { return node.context.bases![node.baseIndex]!; } function nodeParent(node: OverlayNode): OverlayNode | undefined { return node.parentIndex < 0 ? undefined : node.context.nodes[node.parentIndex]; } function storeValue(context: OverlayContext, value: Stored): StoredRef { if (!isContainer(value)) return value; const index = context.stored!.length; context.stored!.push(value); return { index }; } function storedValue(context: OverlayContext, reference: StoredRef): Stored { return typeof reference === "object" && reference !== null ? context.stored![reference.index]! : reference; } function replaceStoredValue(context: OverlayContext, reference: StoredRef, value: Stored): StoredRef { if (isContainer(value)) { if (typeof reference === "object" && reference !== null) { context.stored![reference.index] = value; return reference; } return storeValue(context, value); } if (typeof reference === "object" && reference !== null) context.stored![reference.index] = RELEASED; return value; } function releaseStoredValue(context: OverlayContext, reference: StoredRef): void { if (typeof reference === "object" && reference !== null) context.stored![reference.index] = RELEASED; } function nodeForTarget(target: object): OverlayNode { const node = (target as Record)[NODE] as OverlayNode | undefined; if (node === undefined) throw new TypeError("Cannot use a settled overlay"); return node; } function isSettledContext(context: OverlayContext): boolean { return ( context.overlayReleased || context.status.value === "consumed" || context.status.value === "aborted" || context.status.value === "stale" ); } function assertReadable(context: OverlayContext): void { if (isSettledContext(context)) throw new TypeError("Cannot use a settled overlay"); } function assertWritable(context: OverlayContext): void { assertReadable(context); if (context.status.value !== "open") throw new TypeError("Prepared overlays are read-only"); } function getProperty(node: OverlayNode, property: PropertyKey): unknown { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); if (property === "length") return arrayLength(overlay); if (ARRAY_MUTATORS.has(property)) return arrayMutators[property as keyof typeof arrayMutators]; const index = arrayIndex(property); if (index !== undefined) return getArrayIndex(node, index); return Reflect.get(Array.prototype, property, node.proxy); } if (typeof property === "symbol") return Reflect.get(nodeBase(node), property, node.proxy); const key = String(property); if (!objectHas(node, key)) { if (isObjectDeleted(node, key) || Object.hasOwn(nodeBase(node), key)) return undefined; return Reflect.get(nodeBase(node), property, node.proxy); } const value = objectValue(node, key); if (!isContainer(value)) return value; return createNode(node.context, value, node, 0, key, undefined, hasObjectWrite(node, key)).proxy; } function getArrayIndex(node: OverlayNode, index: number): unknown { const overlay = arrayOverlay(node); if (index >= arrayLength(overlay)) return undefined; const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const value = entryValueAt(node, piece, sourceIndex); if (!isContainer(value)) return value; return createNode( node.context, value, node, piece.kind === "base" ? 1 : 2, sourceIndex, piece.kind === "insert" ? piece.source : undefined, piece.kind === "insert" || hasEntryOverrideAt(overlay, piece, sourceIndex), ).proxy; } function setProperty(node: OverlayNode, property: PropertyKey, supplied: unknown): boolean { assertWritable(node.context); if (typeof property === "symbol") throw new TypeError("Symbol writes are not supported"); if (Array.isArray(nodeBase(node))) { if (property === "length") { setArrayLength(node, toArrayLength(supplied)); return true; } const index = arrayIndex(property); if (index === undefined) throw new TypeError("Only array indices and length can be written"); if (index > arrayLength(arrayOverlay(node))) throw new TypeError("Overlay arrays cannot contain holes"); setArrayIndex(node, index, clonePlacement(supplied)); return true; } const key = String(property); if (supplied === undefined) return deleteProperty(node, key); const stored = clonePlacement(supplied); const current = objectValue(node, key); const wasDeleted = isObjectDeleted(node, key); if (!wasDeleted && !isContainer(stored) && current === stored) return true; setObjectWrite(node, key, stored); if (wasDeleted && Object.hasOwn(nodeBase(node), key)) { if (node.readded === undefined) node.readded = new Set(); node.readded.add(key); } deleteObjectDeletion(node, key); markDirty(node); return true; } function deleteProperty(node: OverlayNode, property: PropertyKey): boolean { assertWritable(node.context); if (typeof property === "symbol") throw new TypeError("Symbol writes are not supported"); if (Array.isArray(nodeBase(node))) throw new TypeError("Overlay arrays cannot contain holes"); const key = String(property); if (!objectHas(node, key)) return true; deleteObjectWrite(node, key); node.readded?.delete(key); setObjectDeletion(node, key); markDirty(node); return true; } function hasProperty(node: OverlayNode, property: PropertyKey): boolean { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { if (property === "length") return true; const index = arrayIndex(property); if (index !== undefined) return index < arrayLength(arrayOverlay(node)); return property in Array.prototype; } return typeof property === "symbol" ? property in nodeBase(node) : objectHas(node, String(property)); } function ownKeys(node: OverlayNode): ArrayLike { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const length = arrayLength(arrayOverlay(node)); const keys = new Array(length + 1); for (let index = 0; index < length; index++) keys[index] = String(index); keys[length] = "length"; return keys; } let existingKeysOnly = node.deleteKey === undefined && node.deletes === undefined && node.readded === undefined && (node.writeKey === undefined || Object.hasOwn(nodeBase(node), node.writeKey)); if (existingKeysOnly && node.writes !== undefined) { for (const key of node.writes.keys()) { if (!Object.hasOwn(nodeBase(node), key)) { existingKeysOnly = false; break; } } } if (existingKeysOnly) return Object.keys(nodeBase(node)); const keys = Object.keys(nodeBase(node)).filter((key) => !isObjectDeleted(node, key) && !node.readded?.has(key)); const seen = new Set(keys); if (node.writeKey !== undefined && !seen.has(node.writeKey)) { keys.push(node.writeKey); seen.add(node.writeKey); } if (node.writes !== undefined) { for (const key of node.writes.keys()) { if (seen.has(key)) continue; keys.push(key); seen.add(key); } } const indices: number[] = []; const strings: string[] = []; for (const key of keys) { const index = arrayIndex(key); if (index === undefined) strings.push(key); else indices.push(index); } indices.sort((left, right) => left - right); return [...indices.map(String), ...strings]; } function getDescriptor(node: OverlayNode, property: PropertyKey): PropertyDescriptor | undefined { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { if (property === "length") { const length = arrayLength(arrayOverlay(node)); (node.target as unknown[]).length = length; return Reflect.getOwnPropertyDescriptor(node.target, "length"); } const index = arrayIndex(property); if (index === undefined || index >= arrayLength(arrayOverlay(node))) return undefined; } else { if (typeof property === "symbol" || !objectHas(node, String(property))) return undefined; } return { configurable: true, enumerable: true, writable: node.context.status.value === "open", value: getProperty(node, property), }; } function hasObjectWrite(node: OverlayNode, key: string): boolean { return node.writeKey === key || (node.writes?.has(key) ?? false); } function setObjectWrite(node: OverlayNode, key: string, value: Stored): void { if (node.writes !== undefined) { const reference = node.writes.get(key); node.writes.set( key, reference === undefined ? storeValue(node.context, value) : replaceStoredValue(node.context, reference, value), ); return; } if (node.writeKey === undefined || node.writeKey === key) { node.writeKey = key; node.writeValue = node.writeValue === undefined ? storeValue(node.context, value) : replaceStoredValue(node.context, node.writeValue, value); return; } node.writes = new Map(); node.writes.set(node.writeKey, node.writeValue!); node.writes.set(key, storeValue(node.context, value)); node.writeKey = undefined; node.writeValue = undefined; } function deleteObjectWrite(node: OverlayNode, key: string): void { if (node.writeKey === key) { node.writeKey = undefined; releaseStoredValue(node.context, node.writeValue!); node.writeValue = undefined; } else { const reference = node.writes?.get(key); if (reference !== undefined) releaseStoredValue(node.context, reference); node.writes?.delete(key); } } function isObjectDeleted(node: OverlayNode, key: string): boolean { return node.deleteKey === key || (node.deletes?.has(key) ?? false); } function setObjectDeletion(node: OverlayNode, key: string): void { if (node.deletes !== undefined) { node.deletes.add(key); return; } if (node.deleteKey === undefined || node.deleteKey === key) { node.deleteKey = key; return; } node.deletes = new Set(); node.deletes.add(node.deleteKey); node.deletes.add(key); node.deleteKey = undefined; } function deleteObjectDeletion(node: OverlayNode, key: string): void { if (node.deleteKey === key) node.deleteKey = undefined; else node.deletes?.delete(key); } function objectHas(node: OverlayNode, key: string): boolean { if (isObjectDeleted(node, key)) return false; return hasObjectWrite(node, key) || Object.hasOwn(nodeBase(node), key); } function objectValue(node: OverlayNode, key: string): Stored { if (node.writeKey === key) return storedValue(node.context, node.writeValue!); const reference = node.writes?.get(key); if (reference !== undefined) return storedValue(node.context, reference); return (nodeBase(node) as Record)[key]!; } function markDirty(node: OverlayNode): void { if (node.dirty) return; node.dirty = true; node.context.dirty.push(node); for (let parent = nodeParent(node); parent !== undefined; parent = nodeParent(parent)) parent.subtreeDirty = true; } function arrayOverlay(node: OverlayNode): ArrayOverlay { if (node.array !== undefined) return node.array; const base = nodeBase(node) as JsonValue[]; const overlay: ArrayOverlay = { root: undefined, pieces: undefined, baseOverrides: undefined, insertOverrides: undefined, structural: false, generation: 0, plan: undefined, seed: 0x9e3779b9, locatedOffset: 0, baseLocations: undefined, insertLocations: undefined, }; if (base.length > 0) overlay.root = createPieceNode(overlay, { kind: "base", start: 0, length: base.length, step: 1 }); node.array = overlay; return overlay; } function baseOverridesForWrite(overlay: ArrayOverlay): Map { if (overlay.baseOverrides === undefined) overlay.baseOverrides = new Map(); return overlay.baseOverrides; } function insertOverridesForWrite(overlay: ArrayOverlay): Map> { if (overlay.insertOverrides === undefined) overlay.insertOverrides = new Map(); return overlay.insertOverrides; } function nextPiecePriority(overlay: ArrayOverlay): number { let value = overlay.seed; value ^= value << 13; value ^= value >>> 17; value ^= value << 5; overlay.seed = value >>> 0; return overlay.seed; } function treeElements(node: PieceNode | undefined): number { return node?.elements ?? 0; } function updatePieceNode(node: PieceNode): void { node.elements = treeElements(node.left) + node.piece.length + treeElements(node.right); } function createPieceNode(overlay: ArrayOverlay, piece: Piece): PieceNode { return { piece, left: undefined, right: undefined, priority: nextPiecePriority(overlay), elements: piece.length }; } function mergePieceTrees(left: PieceNode | undefined, right: PieceNode | undefined): PieceNode | undefined { if (left === undefined) return right; if (right === undefined) return left; if (left.priority >= right.priority) { left.right = mergePieceTrees(left.right, right); updatePieceNode(left); return left; } right.left = mergePieceTrees(left, right.left); updatePieceNode(right); return right; } function splitPieceTree( overlay: ArrayOverlay, root: PieceNode | undefined, index: number, ): [PieceNode | undefined, PieceNode | undefined] { if (root === undefined) return [undefined, undefined]; const leftLength = treeElements(root.left); if (index < leftLength) { const [left, right] = splitPieceTree(overlay, root.left, index); root.left = right; updatePieceNode(root); return [left, root]; } const pieceEnd = leftLength + root.piece.length; if (index > pieceEnd) { const [left, right] = splitPieceTree(overlay, root.right, index - pieceEnd); root.right = left; updatePieceNode(root); return [root, right]; } if (index === leftLength) { const left = root.left; root.left = undefined; updatePieceNode(root); return [left, root]; } if (index === pieceEnd) { const right = root.right; root.right = undefined; updatePieceNode(root); return [root, right]; } const offset = index - leftLength; const first = { ...root.piece, length: offset }; const second = { ...root.piece, start: root.piece.start + root.piece.step * offset, length: root.piece.length - offset, }; return [ mergePieceTrees(root.left, createPieceNode(overlay, first)), mergePieceTrees(createPieceNode(overlay, second), root.right), ]; } function leftmostPieceNode(node: PieceNode): PieceNode { while (node.left !== undefined) node = node.left; return node; } function rightmostPieceNode(node: PieceNode): PieceNode { while (node.right !== undefined) node = node.right; return node; } function mergeablePieces(left: Piece, right: Piece): boolean { if (left.kind !== right.kind) return false; if (left.kind === "insert" && left.source !== (right as InsertPiece).source) return false; if (left.length === 1 && right.length === 1) return Math.abs(right.start - left.start) === 1; return left.step === right.step && left.start + left.step * left.length === right.start; } function joinNormalized( overlay: ArrayOverlay, left: PieceNode | undefined, right: PieceNode | undefined, ): PieceNode | undefined { if (left === undefined) return right; if (right === undefined) return left; const leftPiece = rightmostPieceNode(left).piece; const rightPiece = leftmostPieceNode(right).piece; if (!mergeablePieces(leftPiece, rightPiece)) return mergePieceTrees(left, right); const [leftRest] = splitPieceTree(overlay, left, treeElements(left) - leftPiece.length); const [, rightRest] = splitPieceTree(overlay, right, rightPiece.length); const step = leftPiece.length === 1 ? ((rightPiece.start - leftPiece.start) as 1 | -1) : leftPiece.step; const combined: Piece = leftPiece.kind === "base" ? { kind: "base", start: leftPiece.start, length: leftPiece.length + rightPiece.length, step } : { kind: "insert", source: leftPiece.source, start: leftPiece.start, length: leftPiece.length + rightPiece.length, step, }; return joinNormalized(overlay, joinNormalized(overlay, leftRest, createPieceNode(overlay, combined)), rightRest); } function flattenPieceTree(node: PieceNode | undefined, output: Piece[]): void { if (node === undefined) return; flattenPieceTree(node.left, output); output.push(node.piece); flattenPieceTree(node.right, output); } function piecesOf(overlay: ArrayOverlay): readonly Piece[] { if (overlay.pieces === undefined) { overlay.pieces = []; flattenPieceTree(overlay.root, overlay.pieces); } return overlay.pieces; } function treeFromPieces(overlay: ArrayOverlay, pieces: Piece[]): PieceNode | undefined { mergePieces(pieces); let root: PieceNode | undefined; for (const piece of pieces) root = mergePieceTrees(root, createPieceNode(overlay, piece)); return root; } function replaceAllPieces(overlay: ArrayOverlay, pieces: Piece[]): void { overlay.root = treeFromPieces(overlay, pieces); overlay.pieces = undefined; overlay.baseLocations = undefined; overlay.insertLocations = undefined; } function arrayLength(overlay: ArrayOverlay): number { return treeElements(overlay.root); } function locatePiece(overlay: ArrayOverlay, index: number): Piece { let node = overlay.root; while (node !== undefined) { const leftLength = treeElements(node.left); if (index < leftLength) node = node.left; else if (index >= leftLength + node.piece.length) { index -= leftLength + node.piece.length; node = node.right; } else { overlay.locatedOffset = index - leftLength; return node.piece; } } throw new RangeError("Array overlay index is out of range"); } function arrayIndex(property: PropertyKey): number | undefined { if (typeof property !== "string" || property.length === 0 || property.length > 10) return undefined; if (property === "0") return 0; const first = property.charCodeAt(0); if (first < 49 || first > 57) return undefined; let index = first - 48; for (let offset = 1; offset < property.length; offset++) { const digit = property.charCodeAt(offset) - 48; if (digit < 0 || digit > 9) return undefined; index = index * 10 + digit; if (index >= 4_294_967_295) return undefined; } return index; } function entryValueAt(node: OverlayNode, piece: Piece, sourceIndex: number): Stored { const overlay = arrayOverlay(node); if (piece.kind === "base") { const valueIndex = overlay.baseOverrides?.get(sourceIndex); return valueIndex === undefined ? (nodeBase(node) as JsonValue[])[sourceIndex]! : storedValue(node.context, valueIndex); } const overrides = overlay.insertOverrides?.get(piece.source); const reference = overrides?.has(sourceIndex) ? overrides.get(sourceIndex)! : piece.source.refs[sourceIndex]!; return storedValue(node.context, reference); } function hasEntryOverrideAt(overlay: ArrayOverlay, piece: Piece, sourceIndex: number): boolean { return piece.kind === "base" ? (overlay.baseOverrides?.has(sourceIndex) ?? false) : (overlay.insertOverrides?.get(piece.source)?.has(sourceIndex) ?? false); } function extendRightmostPiece(node: PieceNode, amount: number): void { if (node.right !== undefined) extendRightmostPiece(node.right, amount); else node.piece.length += amount; updatePieceNode(node); } function invalidatePieceCaches(overlay: ArrayOverlay): void { overlay.pieces = undefined; overlay.baseLocations = undefined; overlay.insertLocations = undefined; overlay.plan = undefined; } function replacePieceRange(node: OverlayNode, index: number, remove: number, inserted: Piece[]): void { if (remove === 0 && inserted.length === 0) return; const overlay = arrayOverlay(node); if (remove === 0 && index === arrayLength(overlay) && inserted.length === 1 && overlay.root !== undefined) { const addition = inserted[0]!; const tail = rightmostPieceNode(overlay.root).piece; if ( addition.kind === "insert" && tail.kind === "insert" && tail.step === 1 && tail.start + tail.length === tail.source.refs.length ) { for (let offset = 0; offset < addition.length; offset++) { tail.source.refs.push(addition.source.refs[addition.start + offset]!); } extendRightmostPiece(overlay.root, addition.length); invalidatePieceCaches(overlay); overlay.structural = true; overlay.generation += 1; markDirty(node); return; } } const [left, rest] = splitPieceTree(overlay, overlay.root, index); const [, right] = splitPieceTree(overlay, rest, remove); const middle = treeFromPieces(overlay, inserted); overlay.root = joinNormalized(overlay, joinNormalized(overlay, left, middle), right); invalidatePieceCaches(overlay); overlay.structural = true; overlay.generation += 1; markDirty(node); } function mergePieces(pieces: Piece[]): void { for (let index = 1; index < pieces.length; ) { const left = pieces[index - 1]!; const right = pieces[index]!; const sameSource = left.kind === right.kind && (left.kind === "base" || left.source === (right as InsertPiece).source); if (sameSource && left.length === 1 && right.length === 1 && Math.abs(right.start - left.start) === 1) { left.step = (right.start - left.start) as 1 | -1; left.length = 2; pieces.splice(index, 1); } else if (sameSource && left.step === right.step && left.start + left.step * left.length === right.start) { left.length += right.length; pieces.splice(index, 1); } else index += 1; } } function insertPiece(node: OverlayNode, items: Stored[], start = 0): InsertPiece[] { if (items.length === start) return []; const refs: StoredRef[] = []; for (let index = start; index < items.length; index++) refs.push(storeValue(node.context, items[index]!)); return [{ kind: "insert", source: { refs }, start: 0, length: refs.length, step: 1 }]; } function insertPlacementPiece(node: OverlayNode, values: unknown[], start = 0): InsertPiece[] { if (values.length === start) return []; const storedLength = node.context.stored!.length; try { for (let index = start; index < values.length; index++) values[index] = storeValue(node.context, clonePlacement(values[index])); } catch (error) { node.context.stored!.length = storedLength; throw error; } return [ { kind: "insert", source: { refs: values as StoredRef[] }, start, length: values.length - start, step: 1, }, ]; } function setArrayIndex(node: OverlayNode, index: number, stored: Stored): void { const overlay = arrayOverlay(node); const length = arrayLength(overlay); if (index === length) { replacePieceRange(node, length, 0, insertPiece(node, [stored])); return; } const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const current = entryValueAt(node, piece, sourceIndex); if (!isContainer(stored) && current === stored) return; if (piece.kind === "base") { const existingIndex = overlay.baseOverrides?.get(sourceIndex); if (!isContainer(stored) && stored === (nodeBase(node) as JsonValue[])[sourceIndex]) { if (existingIndex !== undefined) releaseStoredValue(node.context, existingIndex); overlay.baseOverrides?.delete(sourceIndex); } else if (existingIndex === undefined) { baseOverridesForWrite(overlay).set(sourceIndex, storeValue(node.context, stored)); } else overlay.baseOverrides!.set(sourceIndex, replaceStoredValue(node.context, existingIndex, stored)); } else { let overrides = overlay.insertOverrides?.get(piece.source); const existingIndex = overrides?.get(sourceIndex); if (!isContainer(stored) && stored === storedValue(node.context, piece.source.refs[sourceIndex]!)) { if (existingIndex !== undefined) releaseStoredValue(node.context, existingIndex); overrides?.delete(sourceIndex); if (overrides?.size === 0) overlay.insertOverrides?.delete(piece.source); } else { if (overrides === undefined) { overrides = new Map(); insertOverridesForWrite(overlay).set(piece.source, overrides); } if (existingIndex === undefined) overrides.set(sourceIndex, storeValue(node.context, stored)); else overrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex, stored)); } } markDirty(node); } function setArrayLength(node: OverlayNode, next: number): void { const current = arrayLength(arrayOverlay(node)); if (next === current) return; if (next < current) replacePieceRange(node, next, current - next, []); else replacePieceRange( node, current, 0, insertPiece( node, Array.from({ length: next - current }, () => null), ), ); } function toArrayLength(value: unknown): number { const number = Number(value); if (!Number.isInteger(number) || number < 0 || number >= 4_294_967_296) throw new RangeError("Invalid array length"); return number; } function toIntegerOrInfinity(value: unknown): number { const number = Number(value); if (Number.isNaN(number) || number === 0) return 0; return Number.isFinite(number) ? Math.trunc(number) : number; } function clampIndex(value: number, length: number): number { if (value === Number.NEGATIVE_INFINITY) return 0; if (value < 0) return Math.max(length + value, 0); return Math.min(value, length); } function mutatorNode(receiver: unknown): OverlayNode | undefined { if (!isContainer(receiver)) return undefined; const node = Reflect.get(receiver, NODE) as OverlayNode | undefined; if (node === undefined) return undefined; if (!Array.isArray(nodeBase(node))) throw new TypeError("Array mutator called on incompatible receiver"); assertWritable(node.context); return node; } const arrayMutators = { push(this: unknown, ...items: unknown[]): number { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.push, this, items) as number; const length = arrayLength(arrayOverlay(node)); replacePieceRange(node, length, 0, insertPlacementPiece(node, items)); return length + items.length; }, pop(this: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.pop, this, []); const length = arrayLength(arrayOverlay(node)); if (length === 0) return undefined; const value = getArrayIndex(node, length - 1); replacePieceRange(node, length - 1, 1, []); return value; }, shift(this: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.shift, this, []); const length = arrayLength(arrayOverlay(node)); if (length === 0) return undefined; const value = getArrayIndex(node, 0); replacePieceRange(node, 0, 1, []); return value; }, unshift(this: unknown, ...items: unknown[]): number { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.unshift, this, items) as number; replacePieceRange(node, 0, 0, insertPlacementPiece(node, items)); return arrayLength(arrayOverlay(node)); }, splice(this: unknown, ...args: unknown[]): unknown[] { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.splice, this, args) as unknown[]; const length = arrayLength(arrayOverlay(node)); const start = args.length === 0 ? 0 : clampIndex(toIntegerOrInfinity(args[0]), length); const remove = args.length === 0 ? 0 : args.length === 1 ? length - start : Math.min(Math.max(toIntegerOrInfinity(args[1]), 0), length - start); const removed = Array.from({ length: remove }, (_, offset) => getArrayIndex(node, start + offset)); const itemStart = Math.min(2, args.length); const itemCount = Math.max(args.length - 2, 0); replacePieceRange(node, start, remove, insertPlacementPiece(node, args, itemStart)); setArrayLength(node, length - remove + itemCount); return removed; }, reverse(this: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.reverse, this, []); const overlay = arrayOverlay(node); if (arrayLength(overlay) < 2) return node.proxy; const pieces = [...piecesOf(overlay)].reverse(); for (const piece of pieces) { piece.start += piece.step * (piece.length - 1); piece.step = piece.step === 1 ? -1 : 1; } replaceAllPieces(overlay, pieces); overlay.structural = true; overlay.generation += 1; overlay.plan = undefined; markDirty(node); return node.proxy; }, sort(this: unknown, comparator?: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.sort, this, [comparator]); if (comparator !== undefined && typeof comparator !== "function") throw new TypeError("Comparator must be a function"); const overlay = arrayOverlay(node); const insertedSources: InsertSource[] = []; const insertedIndices: number[] = []; const insertedValues: unknown[] = []; const baseValues: unknown[] = new Array((nodeBase(node) as JsonValue[]).length); const order: number[] = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; if (piece.kind === "base") { order.push(sourceIndex); baseValues[sourceIndex] = publicSortValue(node, sourceIndex, insertedSources, insertedIndices); } else { insertedSources.push(piece.source); insertedIndices.push(sourceIndex); const token = -insertedSources.length; order.push(token); insertedValues.push(publicSortValue(node, token, insertedSources, insertedIndices)); } } } const baseSnapshot = new Map(); for (const [index, valueIndex] of overlay.baseOverrides ?? []) baseSnapshot.set(index, storedValue(node.context, valueIndex)); const insertSnapshots = new Map>(); for (const [source, overrides] of overlay.insertOverrides ?? []) { const snapshot = new Map(); for (const [index, valueIndex] of overrides) snapshot.set(index, storedValue(node.context, valueIndex)); insertSnapshots.set(source, snapshot); } const generation = overlay.generation; order.sort((left, right) => { const leftValue = left < 0 ? insertedValues[-left - 1] : baseValues[left]; const rightValue = right < 0 ? insertedValues[-right - 1] : baseValues[right]; if (typeof comparator === "function") return Number(comparator(leftValue, rightValue)); const a = String(leftValue); const b = String(rightValue); return a < b ? -1 : a > b ? 1 : 0; }); if ( baseSnapshot.size > 0 || insertSnapshots.size > 0 || (overlay.baseOverrides?.size ?? 0) > 0 || (overlay.insertOverrides?.size ?? 0) > 0 ) { for (const token of order) { restoreSortOverride(node, token, insertedSources, insertedIndices, baseSnapshot, insertSnapshots); } } const comparatorWasStructural = overlay.generation !== generation; const currentLength = arrayLength(overlay); const samePrefix = currentLength >= order.length && order.every((token, index) => sameSortTokenAt(overlay, index, token, insertedSources, insertedIndices)); if (!samePrefix) { replacePieceRange( node, 0, Math.min(order.length, currentLength), piecesFromSortOrder(order, insertedSources, insertedIndices), ); } if (comparatorWasStructural) deduplicateArrayEntries(node); return node.proxy; }, fill(this: unknown, supplied: unknown, startArg?: unknown, endArg?: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.fill, this, [supplied, startArg, endArg]); const length = arrayLength(arrayOverlay(node)); const start = startArg === undefined ? 0 : clampIndex(toIntegerOrInfinity(startArg), length); const end = endArg === undefined ? length : clampIndex(toIntegerOrInfinity(endArg), length); if (end <= start) return node.proxy; const items = Array.from({ length: end - start }, () => clonePlacement(supplied)); replacePieceRange(node, start, end - start, insertPiece(node, items)); return node.proxy; }, copyWithin(this: unknown, targetArg: unknown, startArg: unknown, endArg?: unknown): unknown { const node = mutatorNode(this); if (node === undefined) return Reflect.apply(Array.prototype.copyWithin, this, [targetArg, startArg, endArg]); const length = arrayLength(arrayOverlay(node)); const target = clampIndex(toIntegerOrInfinity(targetArg), length); const start = clampIndex(toIntegerOrInfinity(startArg), length); const end = endArg === undefined ? length : clampIndex(toIntegerOrInfinity(endArg), length); const count = Math.min(Math.max(end - start, 0), length - target); const values = Array.from({ length: count }, (_, offset) => clonePlacement(getArrayIndex(node, start + offset))); replacePieceRange(node, target, count, insertPiece(node, values)); return node.proxy; }, }; function sortTokenSource(token: number, sources: readonly InsertSource[]): InsertSource | undefined { return token < 0 ? sources[-token - 1] : undefined; } function sortTokenIndex(token: number, indices: readonly number[]): number { return token < 0 ? indices[-token - 1]! : token; } function publicSortValue( node: OverlayNode, token: number, sources: readonly InsertSource[], indices: readonly number[], ): unknown { const overlay = arrayOverlay(node); const source = sortTokenSource(token, sources); const sourceIndex = sortTokenIndex(token, indices); const overrideIndex = source === undefined ? overlay.baseOverrides?.get(sourceIndex) : overlay.insertOverrides?.get(source)?.get(sourceIndex); const value = overrideIndex !== undefined ? storedValue(node.context, overrideIndex) : source === undefined ? (nodeBase(node) as JsonValue[])[sourceIndex]! : storedValue(node.context, source.refs[sourceIndex]!); if (!isContainer(value)) return value; return createNode( node.context, value, node, source === undefined ? 1 : 2, sourceIndex, source, source !== undefined || (overlay.baseOverrides?.has(sourceIndex) ?? false), ).proxy; } function restoreSortOverride( node: OverlayNode, token: number, sources: readonly InsertSource[], indices: readonly number[], baseSnapshot: ReadonlyMap, insertSnapshots: ReadonlyMap>, ): void { const overlay = arrayOverlay(node); const source = sortTokenSource(token, sources); const sourceIndex = sortTokenIndex(token, indices); if (source === undefined) { const existingIndex = overlay.baseOverrides?.get(sourceIndex); const snapshotValue = baseSnapshot.get(sourceIndex); if (snapshotValue !== undefined) { if (existingIndex === undefined) baseOverridesForWrite(overlay).set(sourceIndex, storeValue(node.context, snapshotValue)); else overlay.baseOverrides!.set(sourceIndex, replaceStoredValue(node.context, existingIndex, snapshotValue)); } else { if (existingIndex !== undefined) releaseStoredValue(node.context, existingIndex); overlay.baseOverrides?.delete(sourceIndex); } return; } let overrides = overlay.insertOverrides?.get(source); const existingIndex = overrides?.get(sourceIndex); const snapshotValue = insertSnapshots.get(source)?.get(sourceIndex); if (snapshotValue !== undefined) { if (overrides === undefined) { overrides = new Map(); insertOverridesForWrite(overlay).set(source, overrides); } if (existingIndex === undefined) overrides.set(sourceIndex, storeValue(node.context, snapshotValue)); else overrides.set(sourceIndex, replaceStoredValue(node.context, existingIndex, snapshotValue)); } else { if (existingIndex !== undefined) releaseStoredValue(node.context, existingIndex); overrides?.delete(sourceIndex); if (overrides?.size === 0) overlay.insertOverrides?.delete(source); } } function sameSortTokenAt( overlay: ArrayOverlay, logicalIndex: number, token: number, sources: readonly InsertSource[], indices: readonly number[], ): boolean { const piece = locatePiece(overlay, logicalIndex); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; const tokenSource = sortTokenSource(token, sources); return ( sourceIndex === sortTokenIndex(token, indices) && ((piece.kind === "base" && tokenSource === undefined) || (piece.kind === "insert" && piece.source === tokenSource)) ); } function deduplicateArrayEntries(node: OverlayNode): void { const overlay = arrayOverlay(node); const seenBase = new Set(); const seenInsert = new Map>(); const next: Piece[] = []; let duplicated = false; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; let seen: boolean; if (piece.kind === "base") { seen = seenBase.has(sourceIndex); seenBase.add(sourceIndex); } else { let indices = seenInsert.get(piece.source); if (indices === undefined) { indices = new Set(); seenInsert.set(piece.source, indices); } seen = indices.has(sourceIndex); indices.add(sourceIndex); } if (seen) { duplicated = true; appendMergedPiece( next, insertPiece(node, [clonePlacementStored(entryValueAt(node, piece, sourceIndex), node.context)])[0]!, ); } else appendMergedPiece(next, singletonPiece(piece, sourceIndex)); } } if (!duplicated) return; replaceAllPieces(overlay, next); overlay.structural = true; overlay.generation += 1; overlay.plan = undefined; markDirty(node); } function piecesFromSortOrder( order: readonly number[], sources: readonly InsertSource[], indices: readonly number[], ): Piece[] { const pieces: Piece[] = []; for (const token of order) { const source = token < 0 ? sources[-token - 1] : undefined; const sourceIndex = token < 0 ? indices[-token - 1]! : token; const previous = pieces.at(-1); const sameSource = previous !== undefined && ((source === undefined && previous.kind === "base") || (source !== undefined && previous.kind === "insert" && previous.source === source)); if (previous !== undefined && sameSource) { if (previous.length === 1) { const step = sourceIndex - previous.start; if (step === 1 || step === -1) { previous.step = step; previous.length = 2; continue; } } else if (previous.start + previous.step * previous.length === sourceIndex) { previous.length += 1; continue; } } pieces.push( source === undefined ? { kind: "base", start: sourceIndex, length: 1, step: 1 } : { kind: "insert", source, start: sourceIndex, length: 1, step: 1 }, ); } return pieces; } function appendMergedPiece(pieces: Piece[], piece: Piece): void { const previous = pieces.at(-1); const sameSource = previous !== undefined && previous.kind === piece.kind && (previous.kind === "base" || previous.source === (piece as InsertPiece).source); if (previous !== undefined && sameSource && previous.length === 1 && piece.length === 1) { const step = piece.start - previous.start; if (step === 1 || step === -1) { previous.step = step; previous.length = 2; return; } } if ( previous !== undefined && sameSource && previous.step === piece.step && previous.start + previous.step * previous.length === piece.start ) { previous.length += piece.length; } else pieces.push(piece); } function singletonPiece(piece: Piece, sourceIndex: number): Piece { return piece.kind === "base" ? { kind: "base", start: sourceIndex, length: 1, step: 1 } : { kind: "insert", source: piece.source, start: sourceIndex, length: 1, step: 1 }; } function clonePlacement(value: unknown): Stored { const proxyNode = isContainer(value) ? (Reflect.get(value, NODE) as OverlayNode | undefined) : undefined; return proxyNode === undefined ? copyJson(value) : clonePlacementNode(proxyNode); } function cloneNode(node: OverlayNode): Container { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); const result: JsonValue[] = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; result.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } } return result; } const result = Object.create(Object.getPrototypeOf(nodeBase(node))) as Record; for (const key of ownKeys(node) as string[]) defineData(result, key, cloneStored(objectValue(node, key), node.context)); return result; } function cloneStored(value: Stored, context: OverlayContext): Stored { if (!isContainer(value)) return value; const node = context.rawNodes!.get(value); return node === undefined || (!node.dirty && !node.subtreeDirty) ? value : cloneNode(node); } function clonePlacementNode(node: OverlayNode): Container { assertReadable(node.context); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); const result: JsonValue[] = []; for (const piece of piecesOf(overlay)) { for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; result.push(clonePlacementStored(entryValueAt(node, piece, sourceIndex), node.context)); } } return result; } const result = Object.create(Object.getPrototypeOf(nodeBase(node))) as Record; for (const key of ownKeys(node) as string[]) defineData(result, key, clonePlacementStored(objectValue(node, key), node.context)); return result; } function clonePlacementStored(value: Stored, context: OverlayContext): Stored { if (!isContainer(value)) return copyJson(value); const node = context.rawNodes!.get(value); return node === undefined ? copyJson(value) : clonePlacementNode(node); } function defineData(target: object, key: PropertyKey, value: JsonValue): void { DATA_DESCRIPTOR.value = value; Object.defineProperty(target, key, DATA_DESCRIPTOR); DATA_DESCRIPTOR.value = undefined; } function emitOperations(context: OverlayContext): Op[] { const simple = emitSimpleObjectOperations(context); if (simple !== undefined) return simple; const operations: Op[] = []; const forcedFolds = new Map(); const emissionPaths = new Map(); const denseIndices = new Map(); for (const node of context.dirty) { recordDenseArrayPosition(node, denseIndices); if (Array.isArray(nodeBase(node))) { const overlay = arrayOverlay(node); if (!overlay.structural && (overlay.baseOverrides?.size ?? 0) > 0) { let candidates = denseIndices.get(node); if (candidates === undefined) { candidates = { indices: [], bits: undefined, length: (nodeBase(node) as JsonValue[]).length }; denseIndices.set(node, candidates); } for (const index of overlay.baseOverrides!.keys()) addDenseCandidate(candidates, index); } } } const denseRegions = new Map(); for (const [array, candidates] of denseIndices) { const path = resolvePath(array); if (path === undefined || path.some((segment) => typeof segment === "string" && RESERVED_SEGMENTS.has(segment))) { continue; } const regions = buildDenseRegions(candidates); if (regions.length > 0) denseRegions.set(array, regions); } for (const node of context.dirty) { const path = resolvePath(node); if (path === undefined) continue; if (hasCoveringDenseRegion(node, path, denseRegions)) continue; emissionPaths.set(node, path); if (!Array.isArray(nodeBase(node)) && hasReservedMutation(node)) forcedFolds.set(node, path); const reservedAt = path.findIndex((segment) => typeof segment === "string" && RESERVED_SEGMENTS.has(segment)); if (reservedAt >= 0) { let ancestor = node; for (let depth = path.length; depth > reservedAt; depth--) ancestor = nodeParent(ancestor)!; forcedFolds.set(ancestor, path.slice(0, reservedAt)); } } for (const [node, path] of forcedFolds) emissionPaths.set(node, path); for (const node of denseRegions.keys()) { const path = resolvePath(node); if (path !== undefined && !hasCoveringDenseRegion(node, path, denseRegions)) emissionPaths.set(node, path); } let maxDepth = 0; for (const path of emissionPaths.values()) maxDepth = Math.max(maxDepth, path.length); const buckets: OverlayNode[][] = Array.from({ length: maxDepth + 1 }, () => []); for (const [node, path] of emissionPaths) buckets[path.length]!.push(node); const folded = new Set(); for (const bucket of buckets) { for (const node of bucket) { const path = emissionPaths.get(node)!; if (hasPlacementAncestor(node) || hasFoldedAncestor(node, folded)) continue; if (forcedFolds.has(node)) { emitSet(operations, path, cloneNode(node)); folded.add(node); continue; } if (Array.isArray(nodeBase(node))) emitArrayOperations(node, path, operations, denseRegions.get(node)); else emitObjectOperations(node, path, operations); if (operations.length > MAX_DELTA_OPERATIONS) { locatedDenseArray = undefined; return [["r", cloneNode(context.root!)]]; } } } locatedDenseArray = undefined; return operations; } function emitSimpleObjectOperations(context: OverlayContext): Op[] | undefined { const nodes: OverlayNode[] = []; for (const node of context.dirty) { if (Array.isArray(nodeBase(node)) || hasReservedMutation(node) || hasPlacementAncestor(node)) return undefined; for (let parent = nodeParent(node); parent !== undefined; parent = nodeParent(parent)) { if (Array.isArray(nodeBase(parent))) return undefined; } const path = resolvePath(node); if (path === undefined) continue; for (const segment of path) { if (typeof segment === "string" && RESERVED_SEGMENTS.has(segment)) return undefined; } nodes.push(node); if (nodes.length > MAX_SIMPLE_OBJECT_NODES) return undefined; } // Stable insertion sort avoids comparator/bucket allocations for the usual // handful of dirty object nodes. for (let index = 1; index < nodes.length; index++) { const node = nodes[index]!; const depth = node.preparedPath!.length; let at = index; while (at > 0 && nodes[at - 1]!.preparedPath!.length > depth) { nodes[at] = nodes[at - 1]!; at -= 1; } nodes[at] = node; } const operations: Op[] = []; let canMaterializeDirectly = true; for (const node of nodes) { if (emitObjectOperations(node, node.preparedPath!, operations)) canMaterializeDirectly = false; if (operations.length > MAX_DELTA_OPERATIONS) return [["r", cloneNode(context.root!)]]; } context.simpleObjectMaterialization = canMaterializeDirectly; return operations; } let locatedDenseArray: OverlayNode | undefined; let locatedDenseIndex = 0; function locateDenseArrayPosition(node: OverlayNode): boolean { let child = node; for (let parent = nodeParent(child); parent !== undefined; parent = nodeParent(child)) { if (Array.isArray(nodeBase(parent))) { const overlay = arrayOverlay(parent); if (child.parentKind !== 1 || overlay.structural) return false; const sourceIndex = child.parentKey as number; const valueIndex = overlay.baseOverrides?.get(sourceIndex); const current = valueIndex === undefined ? (nodeBase(parent) as JsonValue[])[sourceIndex] : storedValue(parent.context, valueIndex); if (current !== nodeBase(child)) return false; locatedDenseArray = parent; locatedDenseIndex = sourceIndex; return true; } child = parent; } return false; } function hasCoveringDenseRegion( node: OverlayNode, path: Path, denseRegions: ReadonlyMap, ): boolean { for (let parent = nodeParent(node); parent !== undefined; parent = nodeParent(parent)) { const regions = denseRegions.get(parent); if (regions === undefined) continue; const parentPath = resolvePath(parent); if (parentPath === undefined || path.length <= parentPath.length) continue; let matches = true; for (let index = 0; index < parentPath.length; index++) { if (path[index] !== parentPath[index]) { matches = false; break; } } if (!matches) continue; const index = path[parentPath.length]; if (typeof index === "number" && regionContaining(regions, index)) return true; } return false; } function addDenseCandidate(candidates: DenseCandidates, index: number): void { if (candidates.bits !== undefined) { candidates.bits[index] = 1; return; } candidates.indices.push(index); if (candidates.indices.length < 256) return; candidates.bits = new Uint8Array(candidates.length); for (const existing of candidates.indices) candidates.bits[existing] = 1; candidates.indices.length = 0; } function recordDenseArrayPosition(node: OverlayNode, groups: Map): void { if (!locateDenseArrayPosition(node)) return; let candidates = groups.get(locatedDenseArray!); if (candidates === undefined) { candidates = { indices: [], bits: undefined, length: (nodeBase(locatedDenseArray!) as JsonValue[]).length }; groups.set(locatedDenseArray!, candidates); } addDenseCandidate(candidates, locatedDenseIndex); } function buildDenseRegions(candidates: DenseCandidates): DenseRegion[] { if (candidates.bits === undefined) return []; const regions: DenseRegion[] = []; const bits = candidates.bits; for (let at = 0; at < bits.length; ) { while (at < bits.length && bits[at] === 0) at += 1; if (at === bits.length) break; const start = at; let end = at; let count = 0; let gap = 0; while (at < bits.length) { if (bits[at] !== 0) { count += 1; end = at; gap = 0; } else if (++gap > 1) break; at += 1; } const length = end - start + 1; if (count >= 256 && count * 2 >= length) regions.push({ start, length }); } return regions; } function regionContaining(regions: readonly DenseRegion[], index: number): boolean { for (const region of regions) if (index >= region.start && index < region.start + region.length) return true; return false; } function hasReservedMutation(node: OverlayNode): boolean { if (node.writeKey !== undefined && RESERVED_SEGMENTS.has(node.writeKey)) return true; if (node.deleteKey !== undefined && RESERVED_SEGMENTS.has(node.deleteKey)) return true; if (node.writes !== undefined) { for (const key of node.writes.keys()) if (RESERVED_SEGMENTS.has(key)) return true; } if (node.deletes !== undefined) { for (const key of node.deletes) if (RESERVED_SEGMENTS.has(key)) return true; } return false; } function hasFoldedAncestor(node: OverlayNode, folded: ReadonlySet): boolean { for (let parent = nodeParent(node); parent !== undefined; parent = nodeParent(parent)) if (folded.has(parent)) return true; return false; } function hasPlacementAncestor(node: OverlayNode): boolean { for (let current: OverlayNode | undefined = node; current !== undefined; current = nodeParent(current)) { if (nodeParent(current) !== undefined && current.parentPlacement) return true; } return false; } function emitObjectWrite(node: OverlayNode, path: Path, operations: Op[], key: string, value: Stored): boolean { const nextPath = [...path, key] as unknown as NonEmptyPath; const before = node.readded?.has(key) || !Object.hasOwn(nodeBase(node), key) ? undefined : (nodeBase(node) as Record)[key]; const after = cloneStored(value, node.context); const emitted = emitChangedValue(operations, nextPath, before, after); return isContainer(before) && isContainer(after) && !emitted; } function emitObjectOperations(node: OverlayNode, path: Path, operations: Op[]): boolean { // Immutable replay must encode delete-and-readd explicitly so string-key // insertion order matches the draft. if (node.readded !== undefined) { for (const key of node.readded) { if (Object.hasOwn(nodeBase(node), key)) operations.push(["d", [...path, key] as unknown as NonEmptyPath]); } } let normalizedContainerWrite = false; if (node.writeKey !== undefined) normalizedContainerWrite = emitObjectWrite( node, path, operations, node.writeKey, objectValue(node, node.writeKey), ); if (node.writes !== undefined) { for (const [key, valueIndex] of node.writes) { if (operations.length > MAX_DELTA_OPERATIONS) return normalizedContainerWrite; if (emitObjectWrite(node, path, operations, key, storedValue(node.context, valueIndex))) normalizedContainerWrite = true; } } if (node.deleteKey !== undefined && Object.hasOwn(nodeBase(node), node.deleteKey)) { operations.push(["d", [...path, node.deleteKey] as unknown as NonEmptyPath]); } if (node.deletes !== undefined) { for (const key of node.deletes) { if (operations.length > MAX_DELTA_OPERATIONS) return normalizedContainerWrite; if (Object.hasOwn(nodeBase(node), key)) operations.push(["d", [...path, key] as unknown as NonEmptyPath]); } } return normalizedContainerWrite; } function buildArrayPlan(node: OverlayNode): ArrayPlan { const overlay = arrayOverlay(node); if (overlay.plan !== undefined) return overlay.plan; const base = nodeBase(node) as JsonValue[]; const pieces = piecesOf(overlay); overlay.plan = orderedArrayPlan(base.length, pieces) ?? elementArrayPlan(base.length, pieces); return overlay.plan; } /** * The plan when the retained base elements keep their order, the common case of inserts and removals: computed from the * pieces alone, so appending to a long array costs no walk over its elements. `undefined` when base elements moved. */ function orderedArrayPlan(baseLength: number, pieces: readonly Piece[]): ArrayPlan | undefined { // Retained base ranges in piece order, `[first, last]` each; ordered means strictly ascending without overlap. const ranges: number[] = []; for (const piece of pieces) { if (piece.kind !== "base" || piece.length === 0) continue; if (piece.step !== 1 && piece.length > 1) return undefined; if (ranges.length > 0 && piece.start <= ranges[ranges.length - 1]!) return undefined; ranges.push(piece.start, piece.start + piece.length - 1); } // Unretained runs from the end backwards, as `start, length` pairs: after the last range, between ranges, before the first. const removeRuns: number[] = []; let end = baseLength; for (let index = ranges.length - 2; index >= -2; index -= 2) { const start = index >= 0 ? ranges[index + 1]! + 1 : 0; if (end > start) removeRuns.push(start, end - start); if (index >= 0) end = ranges[index]!; } return { removeRuns, permutation: undefined, insertRuns: insertRunsOf(pieces) }; } /** The plan from a walk over every base element, for pieces that moved base elements. */ function elementArrayPlan(baseLength: number, pieces: readonly Piece[]): ArrayPlan { const retained = new Uint8Array(baseLength); const targetBase: number[] = []; for (const piece of pieces) { if (piece.kind !== "base") continue; for (let offset = 0; offset < piece.length; offset++) { const index = piece.start + piece.step * offset; retained[index] = 1; targetBase.push(index); } } const removeRuns: number[] = []; for (let end = baseLength; end > 0; ) { if (retained[end - 1] !== 0) { end -= 1; continue; } let start = end - 1; while (start > 0 && retained[start - 1] === 0) start -= 1; removeRuns.push(start, end - start); end = start; } const retainedBase: number[] = []; for (let index = 0; index < retained.length; index++) if (retained[index] !== 0) retainedBase.push(index); let permutation: number[] | undefined; if (targetBase.some((value, index) => value !== retainedBase[index])) { const positions = new Map(retainedBase.map((value, index) => [value, index])); permutation = targetBase.map((value) => positions.get(value)!); } return { removeRuns, permutation, insertRuns: insertRunsOf(pieces) }; } /** Runs of consecutive insert pieces as `logical index, first piece, end piece` triples. */ function insertRunsOf(pieces: readonly Piece[]): number[] { const insertRuns: number[] = []; let logicalIndex = 0; for (let pieceIndex = 0; pieceIndex < pieces.length; ) { const piece = pieces[pieceIndex]!; if (piece.kind === "base") { logicalIndex += piece.length; pieceIndex += 1; continue; } const startPiece = pieceIndex; while (pieceIndex < pieces.length && pieces[pieceIndex]!.kind === "insert") { logicalIndex += pieces[pieceIndex]!.length; pieceIndex += 1; } insertRuns.push(logicalIndex - rangeLength(pieces, startPiece, pieceIndex), startPiece, pieceIndex); } return insertRuns; } function rangeLength(pieces: readonly Piece[], start: number, end: number): number { let length = 0; for (let index = start; index < end; index++) length += pieces[index]!.length; return length; } function emitArrayOperations( node: OverlayNode, path: Path, operations: Op[], denseRegions: readonly DenseRegion[] | undefined, ): void { const overlay = arrayOverlay(node); const base = nodeBase(node) as JsonValue[]; if (denseRegions !== undefined) { for (const region of denseRegions) { if (operations.length > MAX_DELTA_OPERATIONS) return; operations.push(["p", path, region.start, region.length, cloneArrayRegion(node, region)]); } } if (overlay.structural) { const plan = buildArrayPlan(node); for (let index = 0; index < plan.removeRuns.length; index += 2) { if (operations.length > MAX_DELTA_OPERATIONS) return; operations.push(["p", path, plan.removeRuns[index]!, plan.removeRuns[index + 1]!, []]); } if (plan.permutation !== undefined) operations.push(["m", path, plan.permutation]); const pieces = piecesOf(overlay); for (let run = 0; run < plan.insertRuns.length; run += 3) { if (operations.length > MAX_DELTA_OPERATIONS) return; const logicalIndex = plan.insertRuns[run]!; const items: JsonValue[] = []; for (let pieceIndex = plan.insertRuns[run + 1]!; pieceIndex < plan.insertRuns[run + 2]!; pieceIndex++) { const piece = pieces[pieceIndex]! as InsertPiece; for (let offset = 0; offset < piece.length; offset++) { const sourceIndex = piece.start + piece.step * offset; items.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } } operations.push(["p", path, logicalIndex, 0, items]); } } for (const [baseIndex, valueIndex] of overlay.baseOverrides ?? []) { if (operations.length > MAX_DELTA_OPERATIONS) return; const index = findEntryIndex(overlay, 1, baseIndex, undefined); if (index === undefined || (denseRegions !== undefined && regionContaining(denseRegions, index))) continue; emitChangedValue( operations, [...path, index] as unknown as NonEmptyPath, base[baseIndex], cloneStored(storedValue(node.context, valueIndex), node.context), ); } } function cloneArrayRegion(node: OverlayNode, region: DenseRegion): JsonValue[] { const overlay = arrayOverlay(node); const result: JsonValue[] = []; for (let index = region.start; index < region.start + region.length; index++) { const piece = locatePiece(overlay, index); const sourceIndex = piece.start + piece.step * overlay.locatedOffset; result.push(cloneStored(entryValueAt(node, piece, sourceIndex), node.context)); } return result; } function emitChangedValue( operations: Op[], path: NonEmptyPath, before: JsonValue | undefined, after: JsonValue, ): boolean { if (!isContainer(after) && before === after) return false; if (isContainer(before) && isContainer(after) && equalTrustedJson(before, after)) return false; if (typeof before === "string" && typeof after === "string") { // Slice/equality stays on V8's rope fast path; startsWith scans the entire // accumulated prefix on every append. if (after.length > before.length && after.slice(0, before.length) === before) { operations.push(["a", path, after.slice(before.length)]); return true; } const shared = overlap(before, after, 65_536); if (shared > 0) { operations.push(["t", path, before.length - shared]); if (after.length > shared) operations.push(["a", path, after.slice(shared)]); return true; } } operations.push(["s", path, after]); return true; } function emitSet(operations: Op[], path: Path, value: JsonValue): void { if (path.length === 0) operations.push(["r", value]); else operations.push(["s", path as NonEmptyPath, value]); } function resolvePath(node: OverlayNode): Path | undefined { if (node.preparedPath !== undefined) return node.preparedPath; const parent = nodeParent(node); if (parent === undefined) { node.preparedPath = []; return node.preparedPath; } const parentPath = resolvePath(parent); if (parentPath === undefined) return undefined; if (node.parentKind === 0) { const key = node.parentKey as string; if (!objectHas(parent, key) || objectValue(parent, key) !== nodeBase(node)) return undefined; node.preparedPath = [...parentPath, key]; return node.preparedPath; } const overlay = arrayOverlay(parent); const index = findEntryIndex(overlay, node.parentKind, node.parentKey as number, node.parentSource); if (index === undefined) return undefined; const piece = locatePiece(overlay, index); if (entryValueAt(parent, piece, node.parentKey as number) !== nodeBase(node)) return undefined; node.preparedPath = [...parentPath, index]; return node.preparedPath; } function ensurePieceLocations(overlay: ArrayOverlay): void { if (overlay.baseLocations !== undefined) return; overlay.baseLocations = []; overlay.insertLocations = new Map(); let logicalStart = 0; for (const piece of piecesOf(overlay)) { const last = piece.start + piece.step * (piece.length - 1); const location = { piece, logicalStart, minimum: Math.min(piece.start, last), maximum: Math.max(piece.start, last), }; if (piece.kind === "base") overlay.baseLocations.push(location); else { let locations = overlay.insertLocations.get(piece.source); if (locations === undefined) { locations = []; overlay.insertLocations.set(piece.source, locations); } locations.push(location); } logicalStart += piece.length; } const byMinimum = (left: PieceLocation, right: PieceLocation): number => left.minimum - right.minimum; overlay.baseLocations.sort(byMinimum); for (const locations of overlay.insertLocations.values()) locations.sort(byMinimum); } function findEntryIndex( overlay: ArrayOverlay, kind: ParentKind, sourceIndex: number, source: InsertSource | undefined, ): number | undefined { if (kind === 1 && !overlay.structural) return sourceIndex; ensurePieceLocations(overlay); const locations = kind === 1 ? overlay.baseLocations! : overlay.insertLocations!.get(source!); if (locations === undefined) return undefined; let low = 0; let high = locations.length; while (low < high) { const middle = (low + high) >>> 1; if (locations[middle]!.minimum <= sourceIndex) low = middle + 1; else high = middle; } const location = locations[low - 1]; if (location === undefined || sourceIndex > location.maximum) return undefined; const offset = (sourceIndex - location.piece.start) / location.piece.step; return offset >= 0 && offset < location.piece.length ? location.logicalStart + offset : undefined; } function ensureOperations(context: OverlayContext): Op[] { if (context.ops !== undefined) return context.ops; assertReadable(context); if (context.replacement) { const base = context.tracker.deref()?.replacementBase(context); context.replacementNoop = base !== undefined && equalTrustedJson(base as unknown as JsonValue, nodeBase(context.root!) as JsonValue); context.ops = context.replacementNoop ? [] : [["r", nodeBase(context.root!)]]; } else context.ops = emitOperations(context); return context.ops; } function equalTrustedJson(left: JsonValue, right: JsonValue): boolean { if (left === right) return true; if (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right)) return false; if (Array.isArray(left)) { const other = right as JsonValue[]; if (left.length !== other.length) return false; for (let index = 0; index < left.length; index++) if (!equalTrustedJson(left[index]!, other[index]!)) return false; return true; } const other = right as Record; const keys = Object.keys(left); if (keys.length !== Object.keys(other).length) return false; for (const key of keys) if (!Object.hasOwn(other, key) || !equalTrustedJson(left[key]!, other[key]!)) return false; return true; } function abortContext(context: OverlayContext): void { if (context.status.value === "aborted" || context.status.value === "consumed" || context.status.value === "stale") return; context.status.value = "aborted"; clearContext(context); } function releaseOverlayReferences(context: OverlayContext): void { context.tracker.deref()?.releaseContext(context); if (locatedDenseArray?.context === context) locatedDenseArray = undefined; context.overlayReleased = true; context.dirty.length = 0; context.nodes.length = 0; context.rawNodes = undefined; context.ops = undefined; context.root = undefined; context.bases = undefined; context.stored = undefined; context.baseValue = undefined; } function clearContext(context: OverlayContext): void { context.tracker.deref()?.releaseContext(context); if (locatedDenseArray?.context === context) locatedDenseArray = undefined; for (const node of context.nodes) { if (Array.isArray(node.target)) node.target.length = 0; Reflect.deleteProperty(node.target, NODE); node.writes?.clear(); node.deletes?.clear(); node.readded?.clear(); if (node.array?.pieces !== undefined) node.array.pieces.length = 0; if (node.array !== undefined) { node.array.root = undefined; node.array.baseLocations = undefined; node.array.insertLocations = undefined; } node.array?.baseOverrides?.clear(); node.array?.insertOverrides?.clear(); node.parentSource = undefined; node.target = RELEASED; node.proxy = RELEASED; node.writeKey = undefined; node.writeValue = undefined; node.writes = undefined; node.deleteKey = undefined; node.deletes = undefined; node.readded = undefined; node.array = undefined; node.subtreeDirty = undefined; node.preparedPath = undefined; } context.dirty.length = 0; context.nodes.length = 0; context.rawNodes = undefined; context.ops = undefined; context.root = undefined; context.bases = undefined; context.stored = undefined; context.baseValue = undefined; context.overlayReleased = true; } function isContainer(value: unknown): value is Container { return value !== null && typeof value === "object"; }