@tanstack/optimistic
Version:
Core optimistic updates library
163 lines (162 loc) • 5 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const d2ts = require("@electric-sql/d2ts");
const store = require("@tanstack/store");
const collection = require("../collection.cjs");
const pipelineCompiler = require("./pipeline-compiler.cjs");
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 d2ts.D2({ initialFrontier: this.version });
const inputs = Object.fromEntries(
Object.entries(collections).map(([key]) => [key, graph.newInput()])
);
const sync = ({ begin, write, commit }) => {
pipelineCompiler.compileQueryPipeline(
query,
inputs
).pipe(
d2ts.output(({ type, data }) => {
if (type === d2ts.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.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 d2ts.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`);
}
store.batch(() => {
Object.entries(this.inputCollections).forEach(([key, collection2]) => {
this.sendChangesToInput(key, collection2.currentStateAsChanges());
});
this.incrementVersion();
this.sendFrontierToAllInputs();
this.runGraph();
});
const changeEffect = new store.Effect({
fn: () => {
store.batch(() => {
Object.entries(this.inputCollections).forEach(([key, collection2]) => {
this.sendChangesToInput(key, collection2.derivedChanges.state);
});
this.incrementVersion();
this.sendFrontierToAllInputs();
this.runGraph();
});
},
deps: Object.values(this.inputCollections).map(
(collection2) => collection2.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`;
}
}
exports.CompiledQuery = CompiledQuery;
exports.compileQuery = compileQuery;
//# sourceMappingURL=compiled-query.cjs.map