Files
dev/third_party/pi/packages/chord/src/delta/tracker.ts
alexion 6081e3e325 chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
2026-10-15 17:58:48 -04:00

2240 lines
78 KiB
TypeScript

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<string, JsonValue>;
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<number, StoredRef> | undefined;
insertOverrides: Map<InsertSource, Map<number, StoredRef>> | undefined;
structural: boolean;
generation: number;
plan: ArrayPlan | undefined;
seed: number;
locatedOffset: number;
baseLocations: PieceLocation[] | undefined;
insertLocations: Map<InsertSource, PieceLocation[]> | 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<string, StoredRef>;
deleteKey?: string;
deletes?: Set<string>;
readded?: Set<string>;
array?: ArrayOverlay;
dirty?: boolean;
subtreeDirty?: boolean;
preparedPath?: Path;
};
type OverlayContext = {
owner: object;
tracker: WeakRef<TrackerImpl<object>>;
baseRevision: number;
status: StatusCell;
root: OverlayNode | undefined;
bases: Container[] | undefined;
stored: Container[] | undefined;
dirty: OverlayNode[];
nodes: OverlayNode[];
rawNodes: WeakMap<object, OverlayNode> | undefined;
ops: Op[] | undefined;
replacement: boolean;
replacementNoop: boolean;
baseValue: object | undefined;
overlayReleased: boolean;
simpleObjectMaterialization: boolean;
registryRef: WeakRef<OverlayContext> | undefined;
};
const NODE = Symbol("chord.delta.overlay.node");
const PREPARED = new WeakMap<object, OverlayContext>();
const RELEASED: Record<string, JsonValue> = {};
const ARRAY_MUTATORS = new Set<PropertyKey>([
"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<T extends object> {
readonly base: T;
readonly value: T;
readonly ops: readonly Op[];
readonly baseRevision: number;
abort(): void;
}
export interface Change<T extends object> {
readonly state: Draft<T>;
prepare(): Prepared<T>;
abort(): void;
}
export interface Tracker<T extends object> {
readonly value: T;
readonly revision: number;
beginChange(): Change<T>;
prepareReplace(value: T): Prepared<T>;
adopt(prepared: Prepared<T>): void;
}
class PreparedImpl<T extends object> implements Prepared<T> {
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<T extends object> implements Change<T> {
#context: OverlayContext | undefined;
#preparedStatus: StatusCell | undefined;
#settled = false;
constructor(context: OverlayContext) {
this.#context = context;
}
get state(): Draft<T> {
const state = this.#context?.root?.proxy;
if (state === undefined) throw new TypeError("Cannot use a settled overlay");
return state as Draft<T>;
}
prepare(): Prepared<T> {
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<T>(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<T extends object> implements Tracker<T> {
readonly #owner = {};
readonly #contexts = new Set<WeakRef<OverlayContext>>();
readonly #selfRef: WeakRef<TrackerImpl<object>>;
#value: T;
#revision = 0;
#pruneBudget = 256;
constructor(initial: T) {
this.#value = initial;
this.#selfRef = new WeakRef(this as unknown as TrackerImpl<object>);
}
get value(): T {
return this.#value;
}
get revision(): number {
return this.#revision;
}
beginChange(): Change<T> {
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<T> {
const context = createContext(this.#selfRef, this.#owner, this.#revision, value, true, this.#value);
context.status.value = "prepared";
this.#register(context);
try {
return materializePrepared<T>(context);
} catch (error) {
context.status.value = "aborted";
clearContext(context);
throw error;
}
}
adopt(prepared: Prepared<T>): 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<T extends object>(initial: T): Tracker<T> {
return new TrackerImpl(initial);
}
function materializePrepared<T extends object>(context: OverlayContext): PreparedImpl<T> {
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<T extends object>(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<T extends object>(
tracker: WeakRef<TrackerImpl<object>>,
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<object> = {
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<PropertyKey, unknown>)[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<object> = 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<PropertyKey, unknown>)[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<PropertyKey, unknown>)[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<string | symbol> {
assertReadable(node.context);
if (Array.isArray(nodeBase(node))) {
const length = arrayLength(arrayOverlay(node));
const keys = new Array<string>(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<string, JsonValue>)[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<number, StoredRef> {
if (overlay.baseOverrides === undefined) overlay.baseOverrides = new Map();
return overlay.baseOverrides;
}
function insertOverridesForWrite(overlay: ArrayOverlay): Map<InsertSource, Map<number, StoredRef>> {
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<number, Stored>();
for (const [index, valueIndex] of overlay.baseOverrides ?? [])
baseSnapshot.set(index, storedValue(node.context, valueIndex));
const insertSnapshots = new Map<InsertSource, Map<number, Stored>>();
for (const [source, overrides] of overlay.insertOverrides ?? []) {
const snapshot = new Map<number, Stored>();
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<number, Stored>,
insertSnapshots: ReadonlyMap<InsertSource, ReadonlyMap<number, Stored>>,
): 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<number>();
const seenInsert = new Map<InsertSource, Set<number>>();
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<string, JsonValue>;
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<string, JsonValue>;
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<OverlayNode, Path>();
const emissionPaths = new Map<OverlayNode, Path>();
const denseIndices = new Map<OverlayNode, DenseCandidates>();
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<OverlayNode, DenseRegion[]>();
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<OverlayNode>();
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<OverlayNode, readonly DenseRegion[]>,
): 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<OverlayNode, DenseCandidates>): 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<OverlayNode>): 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<string, JsonValue>)[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<string, JsonValue>;
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";
}