UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

282 lines (281 loc) 9.13 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const expressions = require("../compiler/expressions.cjs"); const utils = require("./utils.cjs"); const loadMoreCallbackSymbol = /* @__PURE__ */ 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) { const orderByInfo = this.getOrderByInfo(); const trackLoadResult = (result) => { if (result instanceof Promise) { this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise( result ); } }; const onStatusChange = (event) => { const subscription2 = event.subscription; if (event.status === `loadingSubset`) { this.ensureLoadingPromise(subscription2); } else { const deferred = this.subscriptionLoadingPromises.get(subscription2); if (deferred) { this.subscriptionLoadingPromises.delete(subscription2); deferred.resolve(); } } }; let subscription; if (orderByInfo) { subscription = this.subscribeToOrderedChanges( whereExpression, orderByInfo, onStatusChange, trackLoadResult ); } else { const includeInitialState = !this.collectionConfigBuilder.isLazyAlias( this.alias ); subscription = this.subscribeToMatchingChanges( whereExpression, includeInitialState, onStatusChange ); } if (subscription.status === `loadingSubset`) { this.ensureLoadingPromise(subscription); } const unsubscribe = () => { const deferred = this.subscriptionLoadingPromises.get(subscription); if (deferred) { this.subscriptionLoadingPromises.delete(subscription); deferred.resolve(); } subscription.unsubscribe(); }; this.collectionConfigBuilder.currentSyncState.unsubscribeCallbacks.add( unsubscribe ); return subscription; } sendChangesToPipeline(changes, callback) { const changesArray = Array.isArray(changes) ? changes : [...changes]; const filteredChanges = utils.filterDuplicateInserts( changesArray, this.sentToD2Keys ); const input = this.collectionConfigBuilder.currentSyncState.inputs[this.alias]; const sentChanges = utils.sendChangesToInput( input, filteredChanges, this.collection.config.getKey ); const dataLoader = sentChanges > 0 ? callback : void 0; this.collectionConfigBuilder.scheduleGraphRun(dataLoader, { alias: this.alias }); } subscribeToMatchingChanges(whereExpression, includeInitialState, onStatusChange) { const sendChanges = (changes) => { this.sendChangesToPipeline(changes); }; const hints = utils.computeSubscriptionOrderByHints( this.collectionConfigBuilder.query, this.alias ); const onLoadSubsetResult = includeInitialState ? (result) => { if (result instanceof Promise) { this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise( result ); } } : void 0; const subscription = this.collection.subscribeChanges(sendChanges, { ...includeInitialState && { includeInitialState }, whereExpression, onStatusChange, orderBy: hints.orderBy, limit: hints.limit, onLoadSubsetResult }); return subscription; } subscribeToOrderedChanges(whereExpression, orderByInfo, onStatusChange, onLoadSubsetResult) { const { orderBy, offset, limit, index } = orderByInfo; const handleLoadSubsetResult = (result) => { if (result instanceof Promise) { this.pendingOrderedLoadPromise = result; result.finally(() => { if (this.pendingOrderedLoadPromise === result) { this.pendingOrderedLoadPromise = void 0; } }); } onLoadSubsetResult(result); }; this.orderedLoadSubsetResult = handleLoadSubsetResult; const subscriptionHolder = {}; const sendChangesInRange = (changes) => { const changesArray = Array.isArray(changes) ? changes : [...changes]; this.trackSentValues(changesArray, orderByInfo.comparator); const splittedChanges = utils.splitUpdates(changesArray); this.sendChangesToPipelineWithTracking( splittedChanges, subscriptionHolder.current ); }; const subscription = this.collection.subscribeChanges(sendChangesInRange, { whereExpression, onStatusChange }); subscriptionHolder.current = subscription; const truncateUnsubscribe = this.collection.on(`truncate`, () => { this.biggest = void 0; this.lastLoadRequestKey = void 0; this.pendingOrderedLoadPromise = 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, trackLoadSubsetPromise: false, onLoadSubsetResult: handleLoadSubsetResult }); } else { subscription.requestSnapshot({ orderBy: normalizedOrderBy, limit: offset + limit, trackLoadSubsetPromise: false, onLoadSubsetResult: handleLoadSubsetResult }); } 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; } if (this.pendingOrderedLoadPromise) { 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 subscriptionWithLoader = subscription; subscriptionWithLoader[loadMoreCallbackSymbol] ??= this.loadMoreIfNeeded.bind(this, subscription); this.sendChangesToPipeline( changes, 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 cursor = utils.computeOrderedLoadCursor( orderByInfo, this.biggest, this.lastLoadRequestKey, this.alias, n ); if (!cursor) return; this.lastLoadRequestKey = cursor.loadRequestKey; subscription.requestLimitedSnapshot({ orderBy: cursor.normalizedOrderBy, limit: n, minValues: cursor.minValues, trackLoadSubsetPromise: false, onLoadSubsetResult: this.orderedLoadSubsetResult }); } 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) { const result = utils.trackBiggestSentValue( changes, this.biggest, this.sentToD2Keys, comparator ); this.biggest = result.biggest; if (result.shouldResetLoadKey) { this.lastLoadRequestKey = void 0; } } ensureLoadingPromise(subscription) { if (this.subscriptionLoadingPromises.has(subscription)) { return; } let resolve; const promise = new Promise((res) => { resolve = res; }); this.subscriptionLoadingPromises.set(subscription, { resolve }); this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise( promise ); } } exports.CollectionSubscriber = CollectionSubscriber; //# sourceMappingURL=collection-subscriber.cjs.map