UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

225 lines (224 loc) 7.44 kB
import { isWhereSubset, isLoadSubsetRequestSubsumedBy, minusWherePredicates, unionWherePredicates } from "./predicate-utils.js"; import { Func, Value, PropRef } from "./ir.js"; class DeduplicatedLoadSubset { constructor(opts) { this.unlimitedWhere = void 0; this.hasLoadedAllData = false; this.limitedCalls = []; this.inflightCalls = []; this.generation = 0; this.loadSubset = (options) => { if (this.hasLoadedAllData) { this.onDeduplicate?.(options); return true; } if (this.unlimitedWhere !== void 0 && options.where !== void 0) { if (isWhereSubset(options.where, this.unlimitedWhere)) { this.onDeduplicate?.(options); return true; } } if (options.limit !== void 0 || options.cursor !== void 0) { const alreadyLoaded = this.limitedCalls.some( (loaded) => isLoadSubsetRequestSubsumedBy(options, loaded) ); if (alreadyLoaded) { this.onDeduplicate?.(options); return true; } } const matchingInflight = this.inflightCalls.find( (inflight) => !inflight.lease.aborted && isLoadSubsetRequestSubsumedBy(options, inflight.options) ); if (matchingInflight !== void 0) { matchingInflight.lease.attach(options.signal); const prom = matchingInflight.promise; void prom.then(() => this.onDeduplicate?.(options)).catch(() => { }); return prom; } const lease = createSharedAbortLease(options.signal); const trackingOptions = cloneOptions({ ...options, signal: lease.signal }); const loadOptions = cloneOptions({ ...options, signal: lease.signal }); if (this.unlimitedWhere !== void 0 && options.limit === void 0 && options.cursor === void 0) { loadOptions.where = minusWherePredicates(loadOptions.where, this.unlimitedWhere) ?? loadOptions.where; } let resultPromise; try { resultPromise = this._loadSubset(loadOptions); } catch (error) { lease.dispose(); throw error; } if (resultPromise === true) { if (!lease.aborted) this.updateTracking(trackingOptions); lease.dispose(); return true; } else { const capturedGeneration = this.generation; const inflightEntry = { options: trackingOptions, lease, promise: resultPromise.then((result) => { if (capturedGeneration === this.generation && !lease.aborted) { this.updateTracking(trackingOptions); } return result; }).finally(() => { const index = this.inflightCalls.indexOf(inflightEntry); if (index !== -1) { this.inflightCalls.splice(index, 1); } lease.dispose(); }) }; this.inflightCalls.push(inflightEntry); return inflightEntry.promise; } }; this._loadSubset = opts.loadSubset; this.onDeduplicate = opts.onDeduplicate; } /** * Reset all tracking state. * Clears the history of loaded predicates and in-flight calls. * Use this when you want to start fresh, for example after clearing the underlying data store. * * Note: Any in-flight requests will still complete, but they will not update the tracking * state after the reset. This prevents old requests from repopulating cleared state. */ reset() { this.unlimitedWhere = void 0; this.hasLoadedAllData = false; this.limitedCalls = []; this.inflightCalls = []; this.generation++; } updateTracking(options) { if (options.limit === void 0 && options.cursor === void 0) { if (options.where === void 0) { this.hasLoadedAllData = true; this.unlimitedWhere = void 0; this.limitedCalls = []; this.inflightCalls = []; } else if (this.unlimitedWhere === void 0) { this.unlimitedWhere = options.where; } else { this.unlimitedWhere = unionWherePredicates([ this.unlimitedWhere, options.where ]); } } else { this.limitedCalls.push(options); } } } function createSharedAbortLease(initialSignal) { const controller = initialSignal ? new AbortController() : void 0; const listeners = /* @__PURE__ */ new Map(); let hasUnabortableOwner = initialSignal === void 0; let activeAbortableOwners = 0; const abortIfUnused = (reason) => { if (!hasUnabortableOwner && listeners.size > 0 && activeAbortableOwners === 0) { controller?.abort(reason); } }; const attach = (signal) => { if (!signal) { hasUnabortableOwner = true; return; } if (listeners.has(signal)) return; const onAbort = () => { activeAbortableOwners -= 1; abortIfUnused(signal.reason); }; listeners.set(signal, onAbort); if (signal.aborted) { abortIfUnused(signal.reason); } else { activeAbortableOwners += 1; signal.addEventListener(`abort`, onAbort, { once: true }); } }; attach(initialSignal); return { signal: controller?.signal, get aborted() { return controller?.signal.aborted ?? false; }, attach, dispose: () => { for (const [signal, listener] of listeners) { signal.removeEventListener(`abort`, listener); } listeners.clear(); } }; } function cloneOptions(options) { return { ...options, where: options.where ? cloneBasicExpression(options.where, `predicate`) : void 0, orderBy: options.orderBy?.map((clause) => ({ ...clause, expression: cloneBasicExpression(clause.expression), compareOptions: { ...clause.compareOptions } })), cursor: options.cursor ? { ...options.cursor, whereFrom: cloneBasicExpression( options.cursor.whereFrom, `predicate` ), whereCurrent: cloneBasicExpression( options.cursor.whereCurrent, `predicate` ) } : void 0 }; } function cloneBasicExpression(expression, context = `exact`) { switch (expression.type) { case `ref`: return new PropRef([...expression.path]); case `val`: return new Value( context === `comparison` ? snapshotComparisonValue(expression.value) : expression.value ); case `func`: return new Func( expression.name, expression.args.map((arg, index) => { if (context === `predicate` && expression.name === `in` && index === 1 && arg.type === `val` && Array.isArray(arg.value)) { return new Value( arg.value.map((value) => snapshotComparisonValue(value)) ); } const argumentContext = context === `predicate` && isComparisonFunction(expression.name) ? `comparison` : context; return cloneBasicExpression(arg, argumentContext); }) ); } } function isComparisonFunction(name) { return name === `eq` || name === `gt` || name === `gte` || name === `lt` || name === `lte`; } function snapshotComparisonValue(value) { if (value instanceof Date) { return new Date(value.getTime()); } if (typeof Buffer !== `undefined` && value instanceof Buffer) { return Buffer.from(value); } if (value instanceof Uint8Array) { return value.slice(); } return value; } export { DeduplicatedLoadSubset, cloneOptions }; //# sourceMappingURL=subset-dedupe.js.map