UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

382 lines (381 loc) 13 kB
import { canExpressCursorOrder, buildCursorCurrent } from "../../utils/cursor.js"; import { normalizeError } from "../../utils/error.js"; import { runAllCallbacks } from "../../utils/callbacks.js"; import { normalizeOrderByPaths } from "../compiler/expressions.js"; class OrderedSourceLoader { constructor(info, subscription, alias, onResult = () => { }) { this.info = info; this.subscription = subscription; this.alias = alias; this.onResult = onResult; this.hasSettledSourceRequest = false; this.needsFullSourceRecovery = false; this.requesting = false; this.fullSource = `none`; this.settledFiniteAcquisitions = /* @__PURE__ */ new Set(); this.failedAcquisitions = /* @__PURE__ */ new Map(); this.active = true; this.generation = 0; this.info.isRequesting = () => this.requesting; } /** Derive invalidation from actual contributions, not a second cursor. */ onSourceChanges(changes, sentRows) { let hasNewRows = false; for (const change of changes) { const previous = sentRows?.get(change.key); if (change.type !== `insert` && previous !== void 0 && (change.type === `delete` || this.info.comparator(previous, change.value) !== 0)) { this.invalidateSourceOrdering(); return; } if (change.type !== `delete` && previous === void 0) hasNewRows = true; } if (hasNewRows) this.invalidateCursor(); } start() { const { index, limit, offset, orderBy, requiresFullSource } = this.info; if (index) this.subscription.setOrderByIndex(index); if (limit === 0) return; if (requiresFullSource) { this.loadFullSource(); return; } if (!index || orderBy.length !== 1) { this.loadPrefix(offset + limit); return; } this.loadPage(offset + limit); } loadMore(windowOperationGeneration) { if (!this.active || this.info.limit === 0 || this.requesting) return; const mayRetryFailure = this.failedRequest === void 0 || windowOperationGeneration !== void 0 && windowOperationGeneration !== this.failedRequest.windowOperationGeneration; if (!mayRetryFailure) return this.pending; if ((this.failedRequest || this.failedAcquisitions.size > 0) && windowOperationGeneration !== void 0) { if (this.failedRequest) { this.failedRequest.windowOperationGeneration = windowOperationGeneration; } const failedAcquisitions = this.failedAcquisitions; this.failedAcquisitions = /* @__PURE__ */ new Map(); if (failedAcquisitions.size > 0) { this.requesting = true; try { runAllCallbacks(failedAcquisitions.keys()); } finally { this.requesting = false; } if (!this.active) return; } } if (this.fullSource === `failed`) this.fullSource = `none`; else if (this.fullSource !== `none`) return this.pending; if (this.needsFullSourceRecovery || this.info.requiresFullSource) { this.loadFullSource(windowOperationGeneration); return this.pending; } if (!this.info.index || this.info.orderBy.length !== 1) { this.loadPrefix( this.info.offset + this.info.limit, windowOperationGeneration ); return this.pending; } if (!this.info.dataNeeded || this.pending) return this.pending; let count = Math.max( this.info.dataNeeded(), this.hasSettledSourceRequest ? 0 : this.info.offset + this.info.limit ); if (windowOperationGeneration !== void 0 && this.settledSourceBoundary !== void 0) { const needed = this.info.offset + this.info.limit; count = Math.max(count, needed - this.countAcquiredRows()); } if (count > 0) { this.loadPage(count, windowOperationGeneration); } return this.pending; } loadFullSource(windowOperationGeneration) { if (!this.active || this.fullSource !== `none`) return; this.fullSource = `held`; this.requestAndObserve( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `full-source`, windowOperationGeneration ); } loadPrefix(count, windowOperationGeneration) { if (!this.active || this.pending) return; if (this.lastPrefixCount === count) { if ((this.info.dataNeeded?.() ?? 0) > 0) { this.loadFullSource(windowOperationGeneration); } return; } this.requestAndObserve( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ orderBy: normalizeOrderByPaths(this.info.orderBy, this.alias), limit: count, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `ordered`, windowOperationGeneration ); this.lastPrefixCount = count; } resetCursor() { this.generation++; if (this.fullSource === `complete`) this.fullSource = `held`; this.pending = void 0; this.lastBoundary = void 0; this.settledSourceBoundary = void 0; this.invalidateCursor(); } settleFullSourceReplay() { if (this.fullSource === `failed`) { for (const [release, kind] of this.failedAcquisitions) { if (kind === `full-source`) this.failedAcquisitions.delete(release); } this.fullSource = `held`; } if (this.fullSource !== `none`) { this.fullSource = `complete`; this.retireSettledFiniteAcquisitions(); } } retireSettledFiniteAcquisitions() { if (this.fullSource !== `complete` || this.subscription.hasPendingTruncateReplacement) return; const generation = this.generation; runAllCallbacks( Array.from(this.settledFiniteAcquisitions, (release) => () => { if (!this.active || generation !== this.generation) return; this.settledFiniteAcquisitions.delete(release); release(); }) ); } invalidateCursor() { this.lastPage = void 0; this.lastPrefixCount = void 0; } invalidateSourceOrdering() { this.invalidateCursor(); this.requireFullSourceRecovery(); } dispose() { this.active = false; this.resetCursor(); this.failedAcquisitions.clear(); this.settledFiniteAcquisitions.clear(); } countAcquiredRows() { return this.subscription.readOrderedSnapshot({ orderBy: normalizeOrderByPaths(this.info.orderBy, this.alias), limit: this.info.offset + this.info.limit }).filter( ({ value }) => this.info.comparator(value, this.settledSourceBoundary) <= 0 ).length; } loadPage(count, windowOperationGeneration) { if (!this.active || this.pending) return; const startsFromSourcePrefix = this.settledSourceBoundary === void 0; const biggest = this.settledSourceBoundary; let minValues; if (biggest !== void 0) { const value = this.info.valueExtractorForRawRow(biggest); if (!canExpressCursorOrder(this.info.orderBy, [value])) { this.loadPrefix( this.info.offset + this.info.limit, windowOperationGeneration ); return; } minValues = [value]; } const boundary = minValues?.[0]; if (this.lastPage?.count === count && Object.is(this.lastPage.boundary, boundary)) { return; } this.lastPage = { count, boundary }; this.requestAndObserve( (onLoadSubsetResult) => { this.subscription.requestLimitedSnapshot({ orderBy: normalizeOrderByPaths(this.info.orderBy, this.alias), limit: count, minValues, // Local rows seen before the first provider request prove neither // a cursor nor a remote offset. Start the first acquisition at zero. offset: startsFromSourcePrefix ? 0 : this.countAcquiredRows(), trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `ordered`, windowOperationGeneration ); } observe(result, releaseAcquisition, kind, windowOperationGeneration, options) { const isFullSource = kind === `full-source`; const generation = this.generation; const complete = () => { if (this.pending === tracked) this.pending = void 0; if (!this.active) return; if (!isFullSource) { this.settledFiniteAcquisitions.add(releaseAcquisition); this.retireSettledFiniteAcquisitions(); } if (generation !== this.generation) return; if (!isFullSource && (this.failedRequest || this.fullSource !== `none`)) return; this.failedRequest = void 0; if (kind !== `boundary`) { this.hasSettledSourceRequest = true; if (options?.orderBy && !options.cursor) { this.lastPrefixCount = options.limit; } if (!isFullSource && options?.orderBy) { try { this.settledSourceBoundary = this.subscription.readOrderedSnapshot(options).at(-1)?.value ?? this.settledSourceBoundary; } catch (error) { fail(error); } } } if (isFullSource) { this.needsFullSourceRecovery = false; this.fullSource = `complete`; this.retireSettledFiniteAcquisitions(); } if (kind === `ordered`) { this.loadBoundary(windowOperationGeneration); return; } this.loadMore(); }; const settlesAsync = result instanceof Promise; const request = settlesAsync ? result : Promise.resolve(); const fail = (error) => { this.settledFiniteAcquisitions.delete(releaseAcquisition); if (this.pending === tracked) this.pending = void 0; if (!this.active) return; this.requireFullSourceRecovery(); if (generation !== this.generation) return; if (isFullSource) this.fullSource = `failed`; this.recordRequestFailure(windowOperationGeneration); this.failedAcquisitions.set(releaseAcquisition, kind); throw error; }; const tracked = request.then(complete, fail); this.pending = tracked; void tracked.catch(() => { }); this.onResult( tracked, settlesAsync && isFullSource && this.needsFullSourceRecovery ); return tracked; } loadBoundary(windowOperationGeneration) { const biggest = this.settledSourceBoundary; if (biggest === void 0) return; const value = this.info.valueExtractorForRawRow(biggest); const orderBy = normalizeOrderByPaths(this.info.orderBy, this.alias); if (!canExpressCursorOrder(orderBy.slice(0, 1), [value])) { this.loadFullSource(windowOperationGeneration); return this.pending; } if (Object.is(this.lastBoundary, value)) { return this.loadMore(); } const where = buildCursorCurrent(orderBy, [value]); if (!where) { this.loadFullSource(windowOperationGeneration); return this.pending; } this.lastBoundary = value; return this.requestAndObserve( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ where, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `boundary`, windowOperationGeneration ); } requireFullSourceRecovery() { this.settledSourceBoundary = void 0; this.needsFullSourceRecovery = true; } failRequest(observed, error, isFullSource, windowOperationGeneration, cancelObservedSettlement = false) { if (cancelObservedSettlement) { this.generation++; this.pending = void 0; } this.requireFullSourceRecovery(); this.recordRequestFailure(windowOperationGeneration); if (isFullSource) this.fullSource = `none`; try { observed?.release({ error }); } catch { } return error; } /** A failed request blocks ordinary refinement until a new operation. */ recordRequestFailure(windowOperationGeneration) { this.failedRequest = { windowOperationGeneration }; this.invalidateCursor(); this.lastBoundary = void 0; } /** Observe settlement only after all synchronous request work succeeds. */ requestAndObserve(request, kind, windowOperationGeneration) { const isFullSource = kind === `full-source`; let observed; this.requesting = true; let observing = false; try { try { request((result, options, release) => { observed = { result, options, release }; }); } finally { this.requesting = false; } if (!observed) return; observing = true; return this.observe( observed.result, observed.release, kind, windowOperationGeneration, observed.options ); } catch (error) { this.requesting = true; try { throw this.failRequest( observed, normalizeError(error), isFullSource, windowOperationGeneration, observing ); } finally { this.requesting = false; } } } } export { OrderedSourceLoader }; //# sourceMappingURL=ordered-source-loader.js.map