UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

742 lines (741 loc) • 24.4 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const cursor = require("../../utils/cursor.cjs"); const error = require("../../utils/error.cjs"); const callbacks = require("../../utils/callbacks.cjs"); const expressions = require("../compiler/expressions.cjs"); class OrderedPostSettlementError { constructor(cause) { this.cause = cause; } } class OrderedSourceLoader { constructor(info, subscription, alias, onResult = () => { }, canRetryRepair = () => false, getGraphInputRevision, hasPendingJoinedWork = () => false) { this.info = info; this.subscription = subscription; this.alias = alias; this.f_ = onResult; this.f0 = canRetryRepair; this.getGraphInputRevision = getGraphInputRevision; this.hasPendingJoinedWork = hasPendingJoinedWork; this.aL = false; this.ap = false; this.ae = false; this.fT = false; this.l = `none`; this.aF = /* @__PURE__ */ new Map(); this.av = /* @__PURE__ */ new Set(); this.a_ = /* @__PURE__ */ new Map(); this.active = true; this.orderedLoadGeneration = 0; this.orderingInvalidationGeneration = 0; this.cr = 0; this.bG = false; this.info.isRequesting = () => this.fT; } /** 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, requiresFullSource } = this.info; if (index) this.subscription.setOrderByIndex(index); if (limit === 0) return; if (requiresFullSource) { this.loadFullSource(); return; } if (!index) { this.e$(offset + limit); return; } this.fK(offset + limit); } loadMore(windowOperationGeneration, continuesOrderedPrefixRepair = false) { if (!this.active || this.info.limit === 0 || this.fT) return; windowOperationGeneration ??= this.bN?.windowOperationGeneration; continuesOrderedPrefixRepair ||= this.bN?.continuesOrderedPrefixRepair ?? false; if (this.info.joinedFilterSourceId && this.hasPendingJoinedWork()) { if (windowOperationGeneration !== void 0 || continuesOrderedPrefixRepair) { this.bN = { windowOperationGeneration, continuesOrderedPrefixRepair }; } return this.pending; } this.bN = void 0; if (this.S) { if (this.ap && !this.S.isAuthoritativeRepair) { this.S = void 0; } else { this.c3(windowOperationGeneration); if (!this.active || this.pending) return this.pending; } } const mayRetryFailure = this.aj === void 0 || windowOperationGeneration !== void 0 && windowOperationGeneration !== this.aj.windowOperationGeneration; if (!mayRetryFailure) return this.pending; if ((this.aj || this.a_.size > 0) && windowOperationGeneration !== void 0) { this.cG(); this.cr = 0; if (this.aj) { this.aj.windowOperationGeneration = windowOperationGeneration; } this.c7(); if (!this.active) return; } if (this.l === `failed`) { this.l = `none`; } else if (this.l !== `none` && !(continuesOrderedPrefixRepair && this.F === `ordered-repair`)) return this.pending; if (this.ap && !(continuesOrderedPrefixRepair && this.F === `ordered-repair`) || this.info.requiresFullSource) { if (this.ae && !this.info.requiresFullSource) { this.dw(windowOperationGeneration); } else { this.loadFullSource(windowOperationGeneration); } return this.pending; } if (!this.info.index) { const shouldLoad = windowOperationGeneration !== void 0 || (this.info.dataNeeded?.() ?? 0) > 0 || this.bQ !== void 0 && this.bQ < this.info.offset + this.info.limit; if (shouldLoad) { this.e$( this.info.offset + this.info.limit, windowOperationGeneration, continuesOrderedPrefixRepair ); } else if (continuesOrderedPrefixRepair) { this.au(windowOperationGeneration); } return this.pending; } if (!this.info.dataNeeded || this.pending) { if (continuesOrderedPrefixRepair && !this.pending) { this.au(windowOperationGeneration); } return this.pending; } let count = Math.max( this.info.dataNeeded(), this.aL ? 0 : this.info.offset + this.info.limit ); if (windowOperationGeneration !== void 0 && this.ah !== void 0) { const needed = this.info.offset + this.info.limit; count = Math.max(count, needed - this.em()); } if (count > 0) { this.fK( count, windowOperationGeneration, continuesOrderedPrefixRepair ); } else if (continuesOrderedPrefixRepair) { this.au(windowOperationGeneration); } return this.pending; } loadFullSource(windowOperationGeneration) { if (!this.active || this.l !== `none`) return; this.l = `held`; this.F = `full-source`; this.bY( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ ...this.ap ? { refetch: true } : {}, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `full-source`, windowOperationGeneration ); } dw(windowOperationGeneration) { if (!this.active || this.l !== `none`) return; this.W = this.orderingInvalidationGeneration; this.av = /* @__PURE__ */ new Set(); this.l = `held`; this.F = `ordered-repair`; this.bY( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ refetch: true, orderBy: expressions.normalizeOrderByPaths(this.info.orderBy, this.alias), limit: this.info.offset + this.info.limit, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `ordered-repair`, windowOperationGeneration, true ); } e$(count, windowOperationGeneration, continuesOrderedPrefixRepair = false) { if (!this.active || this.pending || this.S) return; if (this.bQ === count) { if ((this.info.dataNeeded?.() ?? 0) > 0) { if (continuesOrderedPrefixRepair) this.bi(); this.loadFullSource(windowOperationGeneration); } else if (continuesOrderedPrefixRepair) { this.au(windowOperationGeneration); } return; } this.bY( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ ...continuesOrderedPrefixRepair ? { refetch: true } : {}, orderBy: expressions.normalizeOrderByPaths(this.info.orderBy, this.alias), limit: count, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `ordered`, windowOperationGeneration, continuesOrderedPrefixRepair ); this.bQ = count; } resetCursor() { this.cG(); this.cr = 0; this.orderedLoadGeneration++; if (this.l === `complete`) this.l = `held`; this.pending = void 0; this.bN = void 0; this.dl = void 0; this.ah = void 0; this.S = void 0; this.invalidateCursor(); } settleFullSourceReplay() { if (this.F === `ordered-repair`) { this.au(); return; } if (this.l === `failed`) { for (const [release, kind] of this.a_) { if (kind === `full-source`) this.a_.delete(release); } this.l = `held`; } if (this.l !== `none`) { this.l = `complete`; this.F = void 0; this.aB(); } } aB(prefix, retained) { if (this.l !== `complete` && !prefix && !retained || this.subscription.hasPendingTruncateReplacement) return; const orderedLoadGeneration = this.orderedLoadGeneration; callbacks.runAllCallbacks( Array.from(this.aF, ([release, count]) => () => { if (retained?.has(release)) return; if (!this.active || orderedLoadGeneration !== this.orderedLoadGeneration) return; if (this.l !== `complete` && !retained && (!prefix || release === prefix.release || count === void 0 || count > prefix.count)) return; this.aF.delete(release); release(); }) ); } invalidateCursor() { if (!this.S?.isAuthoritativeRepair) { this.S = void 0; } this.eZ = void 0; this.bQ = void 0; } invalidateSourceOrdering() { this.orderingInvalidationGeneration++; this.invalidateCursor(); this.dl = void 0; if (!this.ap && this.aL && this.pending === void 0 && this.l === `none`) { this.ae = true; } this.ah = void 0; this.ap = true; } au(windowOperationGeneration) { if (this.F !== `ordered-repair`) return; if (this.W !== this.orderingInvalidationGeneration) { this.av = /* @__PURE__ */ new Set(); this.l = `none`; this.F = void 0; this.W = void 0; if (!this.active) return; if (this.aj) { this.ae = false; this.loadFullSource(windowOperationGeneration); } else { this.ae = true; this.dw(windowOperationGeneration); } return; } const retained = this.av; try { this.aB(void 0, retained); } finally { const hasSupersededAcquisition = [ ...this.aF.keys() ].some((release) => !retained.has(release)); if (!this.active || !hasSupersededAcquisition) { this.av = /* @__PURE__ */ new Set(); this.cG(); this.cr = 0; this.ae = false; this.ap = false; this.l = `none`; this.F = void 0; this.W = void 0; } } } bi() { if (this.F !== `ordered-repair`) return; this.av = /* @__PURE__ */ new Set(); this.ae = false; this.l = `none`; this.F = void 0; this.W = void 0; } dispose() { this.active = false; this.S = void 0; this.resetCursor(); this.a_.clear(); this.aF.clear(); } em() { return this.subscription.readOrderedSnapshot({ orderBy: expressions.normalizeOrderByPaths(this.info.orderBy, this.alias), limit: this.info.offset + this.info.limit }).filter( ({ value }) => this.info.comparator(value, this.ah) <= 0 ).length; } fK(count, windowOperationGeneration, continuesOrderedPrefixRepair = false) { if (!this.active || this.pending || this.S) return; const startsFromSourcePrefix = this.ah === void 0; const biggest = this.ah; let minValues; if (biggest !== void 0) { const value = this.info.valueExtractorForRawRow(biggest); if (!cursor.canExpressCursorOrder(this.info.orderBy, [value])) { this.e$( this.info.offset + this.info.limit, windowOperationGeneration, continuesOrderedPrefixRepair ); return; } minValues = [value]; } const boundary = minValues?.[0]; if (this.eZ?.count === count && Object.is(this.eZ.boundary, boundary)) { if (continuesOrderedPrefixRepair) { this.au(windowOperationGeneration); } return; } this.eZ = { count, boundary }; this.bY( (onLoadSubsetResult) => { this.subscription.requestLimitedSnapshot({ ...continuesOrderedPrefixRepair ? { refetch: true } : {}, orderBy: expressions.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.em(), trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `ordered`, windowOperationGeneration, continuesOrderedPrefixRepair ); } observe(result, releaseAcquisition, kind, windowOperationGeneration, options, continuesOrderedPrefixRepair = false, requestGraphInputRevision) { const isFullSource = kind === `full-source`; const isOrderedRepair = kind === `ordered-repair`; const isAuthoritativeRepair = isFullSource || isOrderedRepair || continuesOrderedPrefixRepair; const retryRepair = isAuthoritativeRepair && this.aL && this.ap && windowOperationGeneration === void 0; const orderedLoadGeneration = this.orderedLoadGeneration; const orderingInvalidationGeneration = this.orderingInvalidationGeneration; const settlesAsync = result instanceof Promise; const canSettleSynchronously = !settlesAsync && windowOperationGeneration === void 0 && !isAuthoritativeRepair && requestGraphInputRevision !== void 0; let synchronousCompletionFailure; const continuation = (continuationKind) => ({ kind: continuationKind, orderedLoadGeneration, orderingInvalidationGeneration, isAuthoritativeRepair, windowOperationGeneration, continuesOrderedPrefixRepair }); const continueOrStage = (next, deferContinuation) => { if (deferContinuation) { this.S = next; } else { this.eI(next); } }; const complete = (trackedRequest, deferContinuation = false) => { if (trackedRequest && this.pending === trackedRequest) { this.pending = void 0; } if (!this.active) return; callbacks.runAllCallbacks([ () => { if (!isFullSource) { const prefixCount = options?.orderBy && !options.cursor ? options.limit : void 0; this.aF.set(releaseAcquisition, prefixCount); if (isOrderedRepair || continuesOrderedPrefixRepair) { this.av.add(releaseAcquisition); } if (!continuesOrderedPrefixRepair) { this.aB(); } if (!continuesOrderedPrefixRepair && orderedLoadGeneration === this.orderedLoadGeneration && prefixCount !== void 0) { this.aB({ release: releaseAcquisition, count: prefixCount }); } } }, () => { if (orderedLoadGeneration !== this.orderedLoadGeneration) return; if (!isAuthoritativeRepair && orderingInvalidationGeneration !== this.orderingInvalidationGeneration) return; if (!isAuthoritativeRepair && (this.aj || this.l !== `none`)) return; if ((isOrderedRepair || continuesOrderedPrefixRepair) && this.aj) { this.bi(); return; } this.aj = void 0; if (kind !== `boundary`) { this.aL = true; if (options?.orderBy && !options.cursor) { this.bQ = options.limit; } if (!isFullSource && options?.orderBy) { try { this.ah = this.subscription.readOrderedSnapshot(options).at(-1)?.value ?? this.ah; } catch (error2) { if (canSettleSynchronously) { synchronousCompletionFailure = { error: error2 }; throw error2; } fail(error2); } } } if ((isOrderedRepair || continuesOrderedPrefixRepair) && this.W !== this.orderingInvalidationGeneration) { this.au(windowOperationGeneration); return; } if (isFullSource) { this.cG(); this.cr = 0; this.ae = false; this.ap = false; this.l = `complete`; this.F = void 0; this.W = void 0; this.aB(); } if (isOrderedRepair || kind === `ordered`) { continueOrStage(continuation(`boundary`), deferContinuation); return; } continueOrStage(continuation(`more`), deferContinuation); } ]); }; const fail = (error2, trackedRequest) => { this.aF.delete(releaseAcquisition); this.S = void 0; if (trackedRequest && this.pending === trackedRequest) { this.pending = void 0; } if (!this.active) return; this.dy(); if (orderedLoadGeneration !== this.orderedLoadGeneration) return; if (isAuthoritativeRepair) { if (isFullSource) { this.l = `failed`; } else { this.l = `none`; this.F = void 0; this.W = void 0; } } this.d5(windowOperationGeneration); this.a_.set(releaseAcquisition, kind); if (retryRepair) this.eh(); throw error2; }; if (canSettleSynchronously) { try { callbacks.runAllCallbacks([ () => complete(void 0, true), () => { if (!synchronousCompletionFailure) { this.f_(true, false, false); } }, () => { if (!synchronousCompletionFailure && this.getGraphInputRevision?.() === requestGraphInputRevision) { this.es(requestGraphInputRevision); } } ]); } catch (error2) { if (synchronousCompletionFailure) { throw synchronousCompletionFailure.error; } throw new OrderedPostSettlementError(error2); } return this.pending; } const request = settlesAsync ? result : Promise.resolve(); const tracked = request.then( () => complete(tracked), (error2) => fail(error2, tracked) ); this.pending = tracked; void tracked.catch(() => { }); this.f_( tracked, isAuthoritativeRepair && this.ap, settlesAsync ); return tracked; } eI(continuation, windowOperationGeneration = continuation.windowOperationGeneration) { if (!this.active || continuation.orderedLoadGeneration !== this.orderedLoadGeneration || !continuation.isAuthoritativeRepair && continuation.orderingInvalidationGeneration !== this.orderingInvalidationGeneration) return; if (continuation.kind === `boundary`) { this.fJ( windowOperationGeneration, continuation.continuesOrderedPrefixRepair ); } else { if (this.info.joinedFilterSourceId && this.hasPendingJoinedWork()) { this.S = continuation; return; } this.loadMore( windowOperationGeneration, continuation.continuesOrderedPrefixRepair ); } } c3(windowOperationGeneration) { const continuation = this.S; if (!continuation) return; this.S = void 0; this.eI(continuation, windowOperationGeneration); } es(graphInputRevision) { if (this.bG) return; this.bG = true; try { while (this.S && !this.pending && this.getGraphInputRevision?.() === graphInputRevision) { this.c3(); } } finally { this.bG = false; } } fJ(windowOperationGeneration, continuesOrderedPrefixRepair = false) { const biggest = this.ah; if (biggest === void 0) { return continuesOrderedPrefixRepair ? this.loadMore(windowOperationGeneration, true) : void 0; } const value = this.info.valueExtractorForRawRow(biggest); const orderBy = expressions.normalizeOrderByPaths(this.info.orderBy, this.alias); if (!cursor.canExpressCursorOrder(orderBy.slice(0, 1), [value])) { if (continuesOrderedPrefixRepair) this.bi(); this.loadFullSource(windowOperationGeneration); return this.pending; } if (Object.is(this.dl, value)) { return this.loadMore( windowOperationGeneration, continuesOrderedPrefixRepair ); } const where = cursor.buildCursorCurrent(orderBy, [value]); if (!where) { if (continuesOrderedPrefixRepair) this.bi(); this.loadFullSource(windowOperationGeneration); return this.pending; } this.dl = value; return this.bY( (onLoadSubsetResult) => { this.subscription.requestSnapshot({ ...continuesOrderedPrefixRepair ? { refetch: true } : {}, where, trackLoadSubsetPromise: false, onLoadSubsetResult }); }, `boundary`, windowOperationGeneration, continuesOrderedPrefixRepair ); } dy() { this.ah = void 0; this.ae = false; this.av.clear(); this.W = void 0; this.ap = true; } cG() { clearTimeout(this.dK); this.dK = void 0; } c7() { const failed = this.a_; this.a_ = /* @__PURE__ */ new Map(); this.fT = true; try { callbacks.runAllCallbacks(failed.keys()); } finally { this.fT = false; } } eh() { if (!this.active || !this.f0() || this.dK !== void 0 || this.cr >= 2) return; const orderedLoadGeneration = this.orderedLoadGeneration; const failedRequest = this.aj; this.dK = setTimeout( () => { this.dK = void 0; const retry = Promise.resolve().then(() => { if (!this.active || !this.f0() || orderedLoadGeneration !== this.orderedLoadGeneration || this.aj !== failedRequest || this.aj?.windowOperationGeneration !== void 0) return; this.c7(); if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- release callbacks can dispose the loader !this.active || orderedLoadGeneration !== this.orderedLoadGeneration || this.subscription.hasPendingTruncateReplacement ) return; this.aj = void 0; this.l = `none`; this.loadFullSource(); }); void retry.catch(() => { }); this.f_(retry, true, true); }, 250 * 2 ** this.cr++ ); } fN(observed, error2, isAuthoritativeRepair, windowOperationGeneration, cancelObservedSettlement = false) { if (cancelObservedSettlement) { this.orderedLoadGeneration++; this.pending = void 0; } this.dy(); this.d5(windowOperationGeneration); if (isAuthoritativeRepair) { this.l = `none`; this.F = void 0; } try { observed?.release({ error: error2 }); } catch { } if (isAuthoritativeRepair && this.aL && windowOperationGeneration === void 0) { this.eh(); } return error2; } /** A failed request blocks ordinary refinement until a new operation. */ d5(windowOperationGeneration) { this.aj = { windowOperationGeneration }; this.S = void 0; this.bN = void 0; this.invalidateCursor(); this.dl = void 0; } /** Observe settlement only after all synchronous request work succeeds. */ bY(request, kind, windowOperationGeneration, continuesOrderedPrefixRepair = false) { const isFullSource = kind === `full-source`; const isAuthoritativeRepair = isFullSource || kind === `ordered-repair` || continuesOrderedPrefixRepair; let observed; this.fT = true; let observing = false; const graphInputRevision = this.getGraphInputRevision?.(); try { try { request((result, options, release) => { observed = { result, options, release }; }); } finally { this.fT = false; } if (!observed) return; observing = true; return this.observe( observed.result, observed.release, kind, windowOperationGeneration, observed.options, continuesOrderedPrefixRepair, graphInputRevision ); } catch (error$1) { if (error$1 instanceof OrderedPostSettlementError) throw error$1.cause; this.fT = true; try { const failure = this.fN( observed, error.normalizeError(error$1), isAuthoritativeRepair, windowOperationGeneration, observing ); if (!observing && isAuthoritativeRepair && this.aL) { const rejected = Promise.reject(failure); void rejected.catch(() => { }); this.f_(rejected, true, true); } throw failure; } finally { this.fT = false; } } } } exports.OrderedSourceLoader = OrderedSourceLoader; //# sourceMappingURL=ordered-source-loader.cjs.map