UNPKG

synopsys

Version:

Synopsys is proof of concept datastore service. It stores facts in terms of entity attribute value triples and allows clients to subscribe to _(datomic inspired)_ queries pushing updates to them when new transactions affect results.

103 lines (89 loc) 2.43 kB
import { Task } from 'datalogia' import * as Codec from './codec.js' export const contentType = 'application/synopsys-sync' class Synchronizer { /** * @param {object} source * @param {import('node:fs/promises').FileHandle} source.file * @param {bigint} source.offset */ constructor({ file, offset }) { this.file = file this.offset = offset const { readable, writable } = new TransformStream() this.readable = readable this.writer = writable.getWriter() /** @type {Set<WritableStreamDefaultWriter<Uint8Array>>} */ this.consumers = new Set() this.poll() } async poll() { for await (const transaction of this.readable) { const chunk = Codec.encodeTransaction(transaction) const { bytesWritten } = await this.file.write(chunk) this.offset += BigInt(bytesWritten) for (const consumer of this.consumers) { consumer.write(chunk) } } } /** * * @param {ReadableStream<Uint8Array>} stream */ async addTransactor(stream) { for await (const transaction of Codec.decode(stream)) { this.writer.write(transaction) } } /** * @param {WritableStream<Uint8Array>} writable * @param {object} options * @param {number} [options.offset] */ async pipeTo(writable, { offset = 0 } = {}) { const writer = writable.getWriter() while (offset < this.offset) { for await (const chunk of this.file.createReadStream({ start: offset, autoClose: false, })) { writer.write(chunk) offset += chunk.length } } this.consumers.add(writer) await writer.closed.catch(() => {}) this.consumers.delete(writer) } } /** * @typedef {Synchronizer} Service */ /** * @param {object} source * @param {import('node:fs/promises').FileHandle} source.file */ export function* open({ file }) { const { size } = yield* Task.wait(file.stat({ bigint: true })) return new Synchronizer({ file, offset: size }) } /** * * @param {Synchronizer} self * @param {object} options * @param {number} options.offset */ export function* pull(self, options) { const { readable, writable } = new TransformStream() self.pipeTo(writable, options) return readable } /** * * @param {Synchronizer} self * @param {ReadableStream<Uint8Array>} stream */ export function* push(self, stream) { return yield* Task.fork(Task.wait(self.addTransactor(stream))) }