@electric-sql/d2ts
Version:
D2TS is a TypeScript implementation of Differential Dataflow.
155 lines • 4.54 kB
JavaScript
import { MultiSet } from './multiset.js';
import { Antichain, v } from './order.js';
import { MessageType, } from './types.js';
/**
* A read handle to a dataflow edge that receives data and frontier updates from a writer.
*
* The data received over this edge are pairs of (version, MultiSet) and the frontier
* updates are either integers (in the one dimensional case) or Antichains (in the general
* case).
*/
export class DifferenceStreamReader {
#queue;
constructor(queue) {
this.#queue = queue;
}
drain() {
const out = [...this.#queue].reverse();
this.#queue.length = 0;
return out;
}
isEmpty() {
return this.#queue.length === 0;
}
probeFrontierLessThan(frontier) {
for (const { type, data } of this.#queue) {
if (type === MessageType.FRONTIER) {
const receivedFrontier = data;
if (frontier.lessEqual(receivedFrontier)) {
return false;
}
}
}
return true;
}
}
/**
* A write handle to a dataflow edge that is allowed to publish data and send
* frontier updates.
*/
export class DifferenceStreamWriter {
#queues = [];
frontier = null;
sendData(version, collection) {
if (typeof version === 'number') {
version = v(version);
}
else if (Array.isArray(version)) {
version = v(version);
}
if (!(collection instanceof MultiSet)) {
collection = new MultiSet(collection);
}
if (this.frontier) {
if (!this.frontier.lessEqualVersion(version)) {
throw new Error('Invalid version');
}
}
const dataMessage = { version, collection };
for (const q of this.#queues) {
q.unshift({
type: MessageType.DATA,
data: dataMessage,
});
}
}
sendFrontier(frontier) {
frontier = Antichain.create(frontier);
if (this.frontier && !this.frontier.lessEqual(frontier)) {
throw new Error('Invalid frontier');
}
this.frontier = frontier;
for (const q of this.#queues) {
q.unshift({ type: MessageType.FRONTIER, data: frontier });
}
}
newReader() {
const q = [];
this.#queues.push(q);
return new DifferenceStreamReader(q);
}
}
/**
* A generic implementation of a dataflow operator (node) that has multiple incoming edges (read handles) and
* one outgoing edge (write handle).
*/
export class Operator {
id;
inputs;
output;
inputFrontiers;
outputFrontier;
constructor(id, inputs, output, initialFrontier) {
this.id = id;
this.inputs = inputs;
this.output = output;
this.inputFrontiers = inputs.map(() => initialFrontier);
this.outputFrontier = initialFrontier;
}
hasPendingWork() {
return this.inputs.some((input) => !input.isEmpty());
}
frontiers() {
return [this.inputFrontiers, this.outputFrontier];
}
}
/**
* A convenience implementation of a dataflow operator that has a handle to one
* incoming stream of data, and one handle to an outgoing stream of data.
*/
export class UnaryOperator extends Operator {
id;
constructor(id, inputA, output, initialFrontier) {
super(id, [inputA], output, initialFrontier);
this.id = id;
}
inputMessages() {
return this.inputs[0].drain();
}
inputFrontier() {
return this.inputFrontiers[0];
}
setInputFrontier(frontier) {
this.inputFrontiers[0] = frontier;
}
}
/**
* A convenience implementation of a dataflow operator that has a handle to two
* incoming streams of data, and one handle to an outgoing stream of data.
*/
export class BinaryOperator extends Operator {
id;
constructor(id, inputA, inputB, output, initialFrontier) {
super(id, [inputA, inputB], output, initialFrontier);
this.id = id;
}
inputAMessages() {
return this.inputs[0].drain();
}
inputAFrontier() {
return this.inputFrontiers[0];
}
setInputAFrontier(frontier) {
this.inputFrontiers[0] = frontier;
}
inputBMessages() {
return this.inputs[1].drain();
}
inputBFrontier() {
return this.inputFrontiers[1];
}
setInputBFrontier(frontier) {
this.inputFrontiers[1] = frontier;
}
}
//# sourceMappingURL=graph.js.map