UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

175 lines (174 loc) • 6.68 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const functions = require("../builder/functions.cjs"); const ir = require("../ir.cjs"); const equalityValueIdentity = require("../equality-value-identity.cjs"); class SubsetDemandController { constructor() { this.states = /* @__PURE__ */ new Map(); this.warnedPlans = /* @__PURE__ */ new Set(); this.valueIdentity = equalityValueIdentity.createValueIdentity(); } setDemand(subscription, plan, keys) { const nextKeys = canonicalizeKeys(keys, this.valueIdentity); const previous = this.states.get(plan.id); const hasFailedCoverage = previous?.segments.some( (segment) => segment.state === `failed` && intersects(segment.keys, nextKeys) ); if (previous && equalKeySets(previous.keys, nextKeys) && !hasFailedCoverage) { return { changed: false, empty: nextKeys.size === 0, ready: true }; } const segments = (previous?.segments ?? []).filter( (segment) => segment.state !== `failed` && intersects(segment.keys, nextKeys) && (!segment.replaces || equalKeySets(segment.keys, nextKeys)) ); const retired = (previous?.segments ?? []).filter( (segment) => !segments.includes(segment) ); const retryReplacement = previous?.segments.some( (segment) => segment.replaces && segment.state === `failed` && equalKeySets(segment.keys, nextKeys) ); const state = { keys: nextKeys, segments }; this.states.set(plan.id, state); for (const segment of retired) releaseSegment(subscription, segment); if (this.states.get(plan.id) !== state) { return { changed: false, empty: false, ready: true }; } if (nextKeys.size === 0) { this.states.delete(plan.id); return { changed: true, empty: true, ready: true }; } const coveredKeys = new Set( segments.flatMap((segment) => [...segment.keys.keys()]) ); const added = new Map( [...nextKeys].filter(([key]) => !coveredKeys.has(key)) ); const removed = previous ? [...previous.keys.keys()].some((key) => !nextKeys.has(key)) : false; const replace = retryReplacement || removed && (added.size > 0 || segments.length > 1); const requestedKeys = replace ? nextKeys : added; if (requestedKeys.size > 0) { const segment = createSegment(plan, requestedKeys); if (replace) segment.replaces = [...segments]; segments.push(segment); const started = startSegment( subscription, segment, () => this.warnUnoptimized(plan) ); if (this.states.get(plan.id) !== state) { return { changed: false, empty: false, ready: true }; } if (!started) throw new Error(`Subset demand snapshot did not start`); if (replace) { if (segment.ready instanceof Promise) { void segment.ready.then( () => this.finishReplacement(subscription, plan.id, segment), () => { } ); } else { this.finishReplacement(subscription, plan.id, segment); } } } const pending = state.segments.filter( (segment) => segment.state === `pending` && intersects(segment.keys, nextKeys) ).map((segment) => segment.ready).filter((ready) => ready instanceof Promise); return { changed: true, empty: false, ready: pending.length > 0 ? Promise.all(pending) : true }; } clear() { for (const state of this.states.values()) { for (const segment of state.segments) segment.abortController.abort(); } this.states.clear(); this.warnedPlans.clear(); this.valueIdentity = equalityValueIdentity.createValueIdentity(); } hasPendingDemand(planId) { return this.states.get(planId)?.segments.some((segment) => segment.state === `pending`) ?? false; } finishReplacement(subscription, planId, replacement) { const state = this.states.get(planId); if (!state?.segments.includes(replacement) || !equalKeySets(replacement.keys, state.keys)) { releaseSegment(subscription, replacement); return; } const replaced = replacement.replaces ?? []; replacement.replaces = void 0; state.segments = [replacement]; for (const segment of replaced) releaseSegment(subscription, segment); } warnUnoptimized(plan) { if (this.warnedPlans.has(plan.id)) return; this.warnedPlans.add(plan.id); const path = plan.path.join(`.`); console.warn( `[TanStack DB]${plan.collectionId ? ` [${plan.collectionId}]` : ``} Join requires an index on "${path}" for efficient loading. Falling back to scanning local data. Consider creating an index on the collection with collection.createIndex((row) => row.${path}) or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.` ); } } function hasPendingJoinedWork(joinedSourceId, lazySourcesCallbacks, subscriptions, isPendingPlan) { return (lazySourcesCallbacks[joinedSourceId]?.plans?.some( (plan) => isPendingPlan(plan.id) ) ?? false) || subscriptions[joinedSourceId]?.status === `loadingSubset`; } function canonicalizeKeys(keys, valueIdentity) { return new Map( [...keys].map((key) => [valueIdentity.serializeEquality(key), key]) ); } function equalKeySets(left, right) { return left.size === right.size && [...left.keys()].every((key) => right.has(key)); } function intersects(left, right) { return [...left.keys()].some((key) => right.has(key)); } function createSegment(plan, keys) { const where = functions.inArray(new ir.PropRef(plan.path), [...keys.values()]); return { keys, where, abortController: new AbortController(), ready: true, state: `starting` }; } function startSegment(subscription, segment, onUnoptimized) { let observed = false; const requested = subscription.requestSnapshot({ where: segment.where, signal: segment.abortController.signal, trackLoadSubsetPromise: false, onUnoptimized, onLoadSubsetResult: (result) => { observed = true; segment.ready = result; segment.state = result instanceof Promise ? `pending` : `settled`; } }); if (segment.ready instanceof Promise) { void segment.ready.then( () => { segment.state = `settled`; }, () => { segment.state = `failed`; } ); } return requested && observed; } function releaseSegment(subscription, segment) { segment.abortController.abort(); try { subscription.releaseSnapshot(segment.where); } catch { } } exports.SubsetDemandController = SubsetDemandController; exports.hasPendingJoinedWork = hasPendingJoinedWork; //# sourceMappingURL=subset-demand-controller.cjs.map