@tanstack/db
Version:
A reactive client store for building super fast apps on sync
742 lines (741 loc) • 24.4 kB
JavaScript
"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