UNPKG

@tanstack/optimistic

Version:

Core optimistic updates library

163 lines (162 loc) 4.84 kB
import { D2, MultiSet, output, MessageType } from "@electric-sql/d2ts"; import { batch, Effect } from "@tanstack/store"; import { Collection } from "../collection.js"; import { compileQueryPipeline } from "./pipeline-compiler.js"; function compileQuery(queryBuilder) { return new CompiledQuery(queryBuilder); } class CompiledQuery { constructor(queryBuilder) { this.state = `compiled`; this.version = 0; const query = queryBuilder._query; const collections = query.collections; if (!collections) { throw new Error(`No collections provided`); } this.inputCollections = collections; const graph = new D2({ initialFrontier: this.version }); const inputs = Object.fromEntries( Object.entries(collections).map(([key]) => [key, graph.newInput()]) ); const sync = ({ begin, write, commit }) => { compileQueryPipeline( query, inputs ).pipe( output(({ type, data }) => { if (type === MessageType.DATA) { begin(); data.collection.getInner().reduce((acc, [[key, value], multiplicity]) => { const changes = acc.get(key) || { deletes: 0, inserts: 0, value }; if (multiplicity < 0) { changes.deletes += Math.abs(multiplicity); } else if (multiplicity > 0) { changes.inserts += multiplicity; changes.value = value; } acc.set(key, changes); return acc; }, /* @__PURE__ */ new Map()).forEach((changes, rawKey) => { const key = rawKey.toString(); const { deletes, inserts, value } = changes; if (inserts && !deletes) { write({ key, value, type: `insert` }); } else if (inserts >= deletes) { write({ key, value, type: `update` }); } else if (deletes > 0) { write({ key, value, type: `delete` }); } }); commit(); } }) ); graph.finalize(); }; this.graph = graph; this.inputs = inputs; this.resultCollection = new Collection({ id: crypto.randomUUID(), // TODO: remove when we don't require any more sync: { sync } }); } get results() { return this.resultCollection; } sendChangesToInput(inputKey, changes) { const input = this.inputs[inputKey]; const multiSetArray = []; for (const change of changes) { if (change.type === `insert`) { multiSetArray.push([change.value, 1]); } else if (change.type === `update`) { multiSetArray.push([change.previousValue, -1]); multiSetArray.push([change.value, 1]); } else { multiSetArray.push([change.value, -1]); } } input.sendData(this.version, new MultiSet(multiSetArray)); } sendFrontierToInput(inputKey) { const input = this.inputs[inputKey]; input.sendFrontier(this.version); } sendFrontierToAllInputs() { Object.entries(this.inputs).forEach(([key]) => { this.sendFrontierToInput(key); }); } incrementVersion() { this.version++; } runGraph() { this.graph.run(); } start() { if (this.state === `running`) { throw new Error(`Query is already running`); } else if (this.state === `stopped`) { throw new Error(`Query is stopped`); } batch(() => { Object.entries(this.inputCollections).forEach(([key, collection]) => { this.sendChangesToInput(key, collection.currentStateAsChanges()); }); this.incrementVersion(); this.sendFrontierToAllInputs(); this.runGraph(); }); const changeEffect = new Effect({ fn: () => { batch(() => { Object.entries(this.inputCollections).forEach(([key, collection]) => { this.sendChangesToInput(key, collection.derivedChanges.state); }); this.incrementVersion(); this.sendFrontierToAllInputs(); this.runGraph(); }); }, deps: Object.values(this.inputCollections).map( (collection) => collection.derivedChanges ) }); this.unsubscribeEffect = changeEffect.mount(); this.state = `running`; return () => { this.stop(); }; } stop() { var _a; (_a = this.unsubscribeEffect) == null ? void 0 : _a.call(this); this.unsubscribeEffect = void 0; this.state = `stopped`; } } export { CompiledQuery, compileQuery }; //# sourceMappingURL=compiled-query.js.map