UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

259 lines (258 loc) 8.66 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const dbIvm = require("@tanstack/db-ivm"); const expressions = require("../compiler/expressions.cjs"); const loadMoreCallbackSymbol = Symbol.for( `@tanstack/db.collection-config-builder` ); class CollectionSubscriber { constructor(alias, collectionId, collection, collectionConfigBuilder) { this.alias = alias; this.collectionId = collectionId; this.collection = collection; this.collectionConfigBuilder = collectionConfigBuilder; this.biggest = void 0; this.subscriptionLoadingPromises = /* @__PURE__ */ new Map(); this.sentToD2Keys = /* @__PURE__ */ new Set(); } subscribe() { const whereClause = this.getWhereClauseForAlias(); if (whereClause) { const whereExpression = expressions.normalizeExpressionPaths(whereClause, this.alias); return this.subscribeToChanges(whereExpression); } return this.subscribeToChanges(); } subscribeToChanges(whereExpression) { let subscription; const orderByInfo = this.getOrderByInfo(); if (orderByInfo) { subscription = this.subscribeToOrderedChanges( whereExpression, orderByInfo ); } else { const includeInitialState = !this.collectionConfigBuilder.isLazyAlias( this.alias ); subscription = this.subscribeToMatchingChanges( whereExpression, includeInitialState ); } const trackLoadPromise = () => { if (!this.subscriptionLoadingPromises.has(subscription)) { let resolve; const promise = new Promise((res) => { resolve = res; }); this.subscriptionLoadingPromises.set(subscription, { resolve }); this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise( promise ); } }; if (subscription.status === `loadingSubset`) { trackLoadPromise(); } const statusUnsubscribe = subscription.on(`status:change`, (event) => { if (event.status === `loadingSubset`) { trackLoadPromise(); } else { const deferred = this.subscriptionLoadingPromises.get(subscription); if (deferred) { this.subscriptionLoadingPromises.delete(subscription); deferred.resolve(); } } }); const unsubscribe = () => { const deferred = this.subscriptionLoadingPromises.get(subscription); if (deferred) { this.subscriptionLoadingPromises.delete(subscription); deferred.resolve(); } statusUnsubscribe(); subscription.unsubscribe(); }; this.collectionConfigBuilder.currentSyncState.unsubscribeCallbacks.add( unsubscribe ); return subscription; } sendChangesToPipeline(changes, callback) { const changesArray = Array.isArray(changes) ? changes : [...changes]; const filteredChanges = []; for (const change of changesArray) { if (change.type === `insert`) { if (this.sentToD2Keys.has(change.key)) { continue; } this.sentToD2Keys.add(change.key); } else if (change.type === `delete`) { this.sentToD2Keys.delete(change.key); } filteredChanges.push(change); } const input = this.collectionConfigBuilder.currentSyncState.inputs[this.alias]; const sentChanges = sendChangesToInput( input, filteredChanges, this.collection.config.getKey ); const dataLoader = sentChanges > 0 ? callback : void 0; this.collectionConfigBuilder.scheduleGraphRun(dataLoader, { alias: this.alias }); } subscribeToMatchingChanges(whereExpression, includeInitialState = false) { const sendChanges = (changes) => { this.sendChangesToPipeline(changes); }; const subscription = this.collection.subscribeChanges(sendChanges, { ...includeInitialState && { includeInitialState }, whereExpression }); return subscription; } subscribeToOrderedChanges(whereExpression, orderByInfo) { const { orderBy, offset, limit, index } = orderByInfo; const sendChangesInRange = (changes) => { const splittedChanges = splitUpdates(changes); this.sendChangesToPipelineWithTracking(splittedChanges, subscription); }; const subscription = this.collection.subscribeChanges(sendChangesInRange, { whereExpression }); const truncateUnsubscribe = this.collection.on(`truncate`, () => { this.biggest = void 0; this.sentToD2Keys.clear(); }); subscription.on(`unsubscribed`, () => { truncateUnsubscribe(); }); const normalizedOrderBy = expressions.normalizeOrderByPaths(orderBy, this.alias); if (index) { subscription.setOrderByIndex(index); subscription.requestLimitedSnapshot({ limit: offset + limit, orderBy: normalizedOrderBy }); } else { subscription.requestSnapshot({ orderBy: normalizedOrderBy, limit: offset + limit }); } return subscription; } // This function is called by maybeRunGraph // after each iteration of the query pipeline // to ensure that the orderBy operator has enough data to work with loadMoreIfNeeded(subscription) { const orderByInfo = this.getOrderByInfo(); if (!orderByInfo) { return true; } const { dataNeeded } = orderByInfo; if (!dataNeeded) { return true; } const n = dataNeeded(); if (n > 0) { this.loadNextItems(n, subscription); } return true; } sendChangesToPipelineWithTracking(changes, subscription) { const orderByInfo = this.getOrderByInfo(); if (!orderByInfo) { this.sendChangesToPipeline(changes); return; } const trackedChanges = this.trackSentValues(changes, orderByInfo.comparator); const subscriptionWithLoader = subscription; subscriptionWithLoader[loadMoreCallbackSymbol] ??= this.loadMoreIfNeeded.bind(this, subscription); this.sendChangesToPipeline( trackedChanges, subscriptionWithLoader[loadMoreCallbackSymbol] ); } // Loads the next `n` items from the collection // starting from the biggest item it has sent loadNextItems(n, subscription) { const orderByInfo = this.getOrderByInfo(); if (!orderByInfo) { return; } const { orderBy, valueExtractorForRawRow, offset } = orderByInfo; const biggestSentRow = this.biggest; const extractedValues = biggestSentRow ? valueExtractorForRawRow(biggestSentRow) : void 0; const minValues = extractedValues !== void 0 ? Array.isArray(extractedValues) ? extractedValues : [extractedValues] : void 0; const normalizedOrderBy = expressions.normalizeOrderByPaths(orderBy, this.alias); subscription.requestLimitedSnapshot({ orderBy: normalizedOrderBy, limit: n, minValues, offset }); } getWhereClauseForAlias() { const sourceWhereClausesCache = this.collectionConfigBuilder.sourceWhereClausesCache; if (!sourceWhereClausesCache) { return void 0; } return sourceWhereClausesCache.get(this.alias); } getOrderByInfo() { const info = this.collectionConfigBuilder.optimizableOrderByCollections[this.collectionId]; if (info && info.alias === this.alias) { return info; } return void 0; } *trackSentValues(changes, comparator) { for (const change of changes) { if (change.type !== `delete`) { if (!this.biggest) { this.biggest = change.value; } else if (comparator(this.biggest, change.value) < 0) { this.biggest = change.value; } } yield change; } } } 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 dbIvm.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; } } } exports.CollectionSubscriber = CollectionSubscriber; //# sourceMappingURL=collection-subscriber.cjs.map