@tanstack/db
Version:
A reactive client store for building super fast apps on sync
180 lines (179 loc) • 5.59 kB
JavaScript
import { MultiSet, serializeValue } from "@tanstack/db-ivm";
import { normalizeOrderByPaths } from "../compiler/expressions.js";
import { buildQuery, getQueryIR } from "../builder/index.js";
function extractCollectionsFromQuery(query) {
const collections = {};
function extractFromSource(source) {
if (source.type === `collectionRef`) {
collections[source.collection.id] = source.collection;
} else if (source.type === `queryRef`) {
extractFromQuery(source.query);
}
}
function extractFromQuery(q) {
if (q.from) {
extractFromSource(q.from);
}
if (q.join && Array.isArray(q.join)) {
for (const joinClause of q.join) {
if (joinClause.from) {
extractFromSource(joinClause.from);
}
}
}
}
extractFromQuery(query);
return collections;
}
function extractCollectionFromSource(query) {
const from = query.from;
if (from.type === `collectionRef`) {
return from.collection;
} else if (from.type === `queryRef`) {
return extractCollectionFromSource(from.query);
}
throw new Error(
`Failed to extract collection. Invalid FROM clause: ${JSON.stringify(query)}`
);
}
function extractCollectionAliases(query) {
const aliasesById = /* @__PURE__ */ new Map();
function recordAlias(source) {
if (!source) return;
if (source.type === `collectionRef`) {
const { id } = source.collection;
const existing = aliasesById.get(id);
if (existing) {
existing.add(source.alias);
} else {
aliasesById.set(id, /* @__PURE__ */ new Set([source.alias]));
}
} else if (source.type === `queryRef`) {
traverse(source.query);
}
}
function traverse(q) {
if (!q) return;
recordAlias(q.from);
if (q.join) {
for (const joinClause of q.join) {
recordAlias(joinClause.from);
}
}
}
traverse(query);
return aliasesById;
}
function buildQueryFromConfig(config) {
if (typeof config.query === `function`) {
return buildQuery(config.query);
}
return getQueryIR(config.query);
}
function sendChangesToInput(input, changes, getKey) {
const multiSetArray = [];
for (const change of changes) {
const key = getKey(change.value);
if (change.type === `insert`) {
multiSetArray.push([[key, change.value], 1]);
} else if (change.type === `update`) {
multiSetArray.push([[key, change.previousValue], -1]);
multiSetArray.push([[key, change.value], 1]);
} else {
multiSetArray.push([[key, change.value], -1]);
}
}
if (multiSetArray.length !== 0) {
input.sendData(new MultiSet(multiSetArray));
}
return multiSetArray.length;
}
function* splitUpdates(changes) {
for (const change of changes) {
if (change.type === `update`) {
yield { type: `delete`, key: change.key, value: change.previousValue };
yield { type: `insert`, key: change.key, value: change.value };
} else {
yield change;
}
}
}
function filterDuplicateInserts(changes, sentKeys) {
const filtered = [];
for (const change of changes) {
if (change.type === `insert`) {
if (sentKeys.has(change.key)) {
continue;
}
sentKeys.add(change.key);
} else if (change.type === `delete`) {
sentKeys.delete(change.key);
}
filtered.push(change);
}
return filtered;
}
function trackBiggestSentValue(changes, current, sentKeys, comparator) {
let biggest = current;
let shouldResetLoadKey = false;
for (const change of changes) {
if (change.type === `delete`) continue;
const isNewKey = !sentKeys.has(change.key);
if (biggest === void 0) {
biggest = change.value;
shouldResetLoadKey = true;
} else if (comparator(biggest, change.value) < 0) {
biggest = change.value;
shouldResetLoadKey = true;
} else if (isNewKey) {
shouldResetLoadKey = true;
}
}
return { biggest, shouldResetLoadKey };
}
function computeSubscriptionOrderByHints(query, alias) {
const { orderBy, limit, offset } = query;
const effectiveLimit = limit !== void 0 && offset !== void 0 ? limit + offset : limit;
const normalizedOrderBy = orderBy ? normalizeOrderByPaths(orderBy, alias) : void 0;
const canPassOrderBy = normalizedOrderBy?.every((clause) => {
const exp = clause.expression;
if (exp.type !== `ref`) return false;
const path = exp.path;
return Array.isArray(path) && path.length === 1;
}) ?? false;
return {
orderBy: canPassOrderBy ? normalizedOrderBy : void 0,
limit: canPassOrderBy ? effectiveLimit : void 0
};
}
function computeOrderedLoadCursor(orderByInfo, biggestSentRow, lastLoadRequestKey, alias, limit) {
const { orderBy, valueExtractorForRawRow, offset } = orderByInfo;
const extractedValues = biggestSentRow ? valueExtractorForRawRow(biggestSentRow) : void 0;
let minValues;
if (extractedValues !== void 0) {
minValues = Array.isArray(extractedValues) ? extractedValues : [extractedValues];
}
const loadRequestKey = serializeValue({
minValues: minValues ?? null,
offset,
limit
});
if (lastLoadRequestKey === loadRequestKey) {
return void 0;
}
const normalizedOrderBy = normalizeOrderByPaths(orderBy, alias);
return { minValues, normalizedOrderBy, loadRequestKey };
}
export {
buildQueryFromConfig,
computeOrderedLoadCursor,
computeSubscriptionOrderByHints,
extractCollectionAliases,
extractCollectionFromSource,
extractCollectionsFromQuery,
filterDuplicateInserts,
sendChangesToInput,
splitUpdates,
trackBiggestSentValue
};
//# sourceMappingURL=utils.js.map