UNPKG

@electric-sql/d2ts

Version:

D2TS is a TypeScript implementation of Differential Dataflow.

244 lines 6.91 kB
import { WeakRefMap } from './utils.js'; const versionCache = new WeakRefMap(); /** * Factory function for creating cached Version objects. * Ensures only one object exists for each unique version, these can then safely be * used as keys in maps etc. */ export function v(version) { const normalized = Array.isArray(version) ? version : [version]; const hash = JSON.stringify(normalized); let cached = versionCache.get(hash); if (!cached) { cached = new Version(normalized); versionCache.set(hash, cached); } return cached; } /** * A partially, or totally ordered version (time), consisting of a tuple of integers. * * All versions within a scope of a dataflow must have the same dimension/number * of coordinates. One dimensional versions are totally ordered. Multidimensional * versions are partially ordered by the product partial order. */ export class Version { #inner; constructor(version) { if (typeof version === 'number') { this.#validateNumber(version); this.#inner = [version]; } else { version.forEach(this.#validateNumber); this.#inner = [...version]; } } toString() { return `Version(${JSON.stringify(this.#inner)})`; } toJSON() { return JSON.stringify(Array.from(this.getInner())); } static fromJSON(json) { return v(JSON.parse(json)); } #validateNumber(n) { if (n < 0 || !Number.isInteger(n)) { throw new Error('Version numbers must be non-negative integers'); } } #validate(other) { if (this.#inner.length !== other.#inner.length) { throw new Error('Version dimensions must match'); } } equals(other) { return (this.#inner.length === other.#inner.length && this.#inner.every((v, i) => v === other.#inner[i])); } lessThan(other) { if (this.lessEqual(other) && !this.equals(other)) { return true; } return false; } lessEqual(other) { this.#validate(other); return this.#inner.every((v, i) => v <= other.#inner[i]); } join(other) { this.#validate(other); const out = this.#inner.map((v, i) => Math.max(v, other.#inner[i])); return v(out); } meet(other) { this.#validate(other); const out = this.#inner.map((v, i) => Math.min(v, other.#inner[i])); return v(out); } advanceBy(frontier) { // The proof for this is in the sharing arrangements paper. if (frontier.isEmpty()) { return this; } let result = this.join(frontier.elements[0]); for (const elem of frontier.elements) { result = result.meet(this.join(elem)); } return result; } extend() { return v([...this.#inner, 0]); } truncate() { const elements = [...this.#inner]; elements.pop(); return v(elements); } applyStep(step) { if (step <= 0) { throw new Error('Step must be positive'); } const elements = [...this.#inner]; elements[elements.length - 1] += step; return v(elements); } getInner() { return this.#inner; } } /** * A minimal set of incomparable versions. * * This keeps the min antichain. */ export class Antichain { #inner; constructor(elements) { this.#inner = []; for (const element of elements) { this.#insert(element); } } static create(value) { if (value instanceof Antichain) { return value; } else if (Array.isArray(value)) { if (value.every((v) => v instanceof Version)) { return new Antichain(value); } else { return new Antichain(value.map((n) => v(n))); } } else if (value instanceof Version) { return new Antichain([value]); } else if (typeof value === 'number') { return new Antichain([v(value)]); } else { throw new Error('Invalid value for Antichain'); } } toString() { return `Antichain(${JSON.stringify(this.#inner.map((v) => v.getInner()))})`; } #insert(element) { for (const e of this.#inner) { if (e.lessEqual(element)) { return; } } this.#inner = this.#inner.filter((x) => !element.lessEqual(x)); this.#inner.push(element); } meet(other) { const out = new Antichain([]); for (const element of this.#inner) { out.#insert(element); } for (const element of other.elements) { out.#insert(element); } return out; } equals(other) { if (this === other) { return true; } if (this.#inner.length !== other.elements.length) { return false; } const sorted1 = [...this.#inner].sort(); const sorted2 = [...other.elements].sort(); return sorted1.every((v, i) => v.equals(sorted2[i])); } lessThan(other) { return this.lessEqual(other) && !this.equals(other); } lessEqual(other) { for (const o of other.elements) { let lessEqual = false; for (const s of this.#inner) { if (s.lessEqual(o)) { lessEqual = true; break; } } if (!lessEqual) { return false; } } return true; } lessEqualVersion(version) { for (const elem of this.#inner) { if (elem.lessEqual(version)) { return true; } } return false; } isEmpty() { return this.#inner.length === 0; } extend() { const out = new Antichain([]); for (const elem of this.#inner) { out.#insert(elem.extend()); } return out; } truncate() { const out = new Antichain([]); for (const elem of this.#inner) { out.#insert(elem.truncate()); } return out; } applyStep(step) { const out = new Antichain([]); for (const elem of this.#inner) { out.#insert(elem.applyStep(step)); } return out; } get elements() { return [...this.#inner]; } toJSON() { return JSON.stringify(this.#inner.map((v) => v.getInner())); } static fromJSON(json) { return new Antichain(JSON.parse(json).map((version) => v(version))); } } export class Frontier extends Antichain { constructor(...elements) { super(elements); } } //# sourceMappingURL=order.js.map