UNPKG

@electric-sql/d2ts

Version:

D2TS is a TypeScript implementation of Differential Dataflow.

155 lines 4.54 kB
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