@electric-sql/d2ts
Version:
D2TS is a TypeScript implementation of Differential Dataflow.
244 lines • 6.91 kB
JavaScript
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