@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 19.3 kB
Source Map (JSON)
{"version":3,"file":"subset-demand-controller.cjs","sources":["../../../../src/query/live/subset-demand-controller.ts"],"sourcesContent":["import { inArray } from '../builder/functions.js'\nimport { PropRef } from '../ir.js'\nimport { createValueIdentity } from '../equality-value-identity.js'\nimport { codedMessage, devBuild } from '../../error-message.js'\nimport { normalizeError } from '../../utils/error.js'\nimport type { ValueIdentity } from '../equality-value-identity.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type {\n LazyCollectionCallbacks,\n LazyDemandPlan,\n} from '../compiler/joins.js'\nimport type { BasicExpression } from '../ir.js'\n\ntype DemandSegment = {\n keys: Map<string, unknown>\n where: BasicExpression<boolean>\n abortController: AbortController\n ready: true | Promise<void>\n state: `starting` | `pending` | `settled` | `failed`\n replaces?: Array<DemandSegment>\n}\n\ntype DemandState = {\n keys: Map<string, unknown>\n segments: Array<DemandSegment>\n}\n\nexport type DemandUpdate = {\n changed: boolean\n empty: boolean\n ready: Promise<Array<unknown>> | true\n}\n\n/**\n * Keeps lazy subset requests aligned with the current relation of demanded\n * keys. Growth loads only new keys. Churn replaces fragmented coverage after\n * the replacement applies, so prior coverage remains live in the meantime.\n */\nexport class SubsetDemandController {\n private readonly states = new Map<string, DemandState>()\n\n /**\n * `onKeyOutcome`: the latest load of a key the plan needs failed with\n * `error`; without one, a load of the key succeeded or the plan no longer\n * needs it. A request error belongs to the key's failed request.\n */\n constructor(\n private readonly onKeyOutcome?: (\n planId: string,\n key: string,\n error?: Error,\n ) => void,\n ) {}\n private readonly warnedPlans = new Set<string>()\n private valueIdentity = createValueIdentity()\n\n /**\n * A truncate's or a reload's replay was judged: its verdict decides every\n * request error of the demand it replayed. Its success clears them, and\n * its failure replaces them with the replay's own failures, which the\n * query reports for the replay.\n */\n clearRequestErrors(): void {\n for (const [planId, state] of this.states)\n for (const key of state.keys.keys()) this.onKeyOutcome?.(planId, key)\n }\n\n setDemand(\n subscription: CollectionSubscription,\n plan: LazyDemandPlan,\n keys: Set<unknown>,\n ): DemandUpdate {\n const nextKeys = canonicalizeKeys(keys, this.valueIdentity)\n const previous = this.states.get(plan.id)\n const hasFailedCoverage = previous?.segments.some(\n (segment) =>\n segment.state === `failed` && intersects(segment.keys, nextKeys),\n )\n if (\n previous &&\n equalKeySets(previous.keys, nextKeys) &&\n !hasFailedCoverage\n ) {\n return { changed: false, empty: nextKeys.size === 0, ready: true }\n }\n\n const segments = (previous?.segments ?? []).filter(\n (segment) =>\n segment.state !== `failed` &&\n intersects(segment.keys, nextKeys) &&\n (!segment.replaces || equalKeySets(segment.keys, nextKeys)),\n )\n const retired = (previous?.segments ?? []).filter(\n (segment) => !segments.includes(segment),\n )\n const retryReplacement = previous?.segments.some(\n (segment) =>\n segment.replaces &&\n segment.state === `failed` &&\n equalKeySets(segment.keys, nextKeys),\n )\n const state: DemandState = { keys: nextKeys, segments }\n this.states.set(plan.id, state)\n // A key the plan no longer needs shows no request error.\n for (const key of previous?.keys.keys() ?? [])\n if (!nextKeys.has(key)) this.onKeyOutcome?.(plan.id, key)\n\n // Law 9: a demand change is one step. Start its new demand before it\n // releases the retired segments, and release them together, so an open\n // replay keeps the new demand and is judged once, after the whole change.\n const release = () => releaseSegments(subscription, retired)\n\n if (nextKeys.size === 0) {\n this.states.delete(plan.id)\n release()\n return { changed: true, empty: true, ready: true }\n }\n\n const coveredKeys = new Set(\n segments.flatMap((segment) => [...segment.keys.keys()]),\n )\n const added = new Map(\n [...nextKeys].filter(([key]) => !coveredKeys.has(key)),\n )\n const removed = previous\n ? [...previous.keys.keys()].some((key) => !nextKeys.has(key))\n : false\n const replace =\n retryReplacement || (removed && (added.size > 0 || segments.length > 1))\n const requestedKeys = replace ? nextKeys : added\n // A replacement alone decides the demand: the segments it supersedes are\n // released, and aborted, once it settles.\n let replacement: DemandSegment | undefined\n try {\n if (requestedKeys.size > 0) {\n const segment = createSegment(plan, requestedKeys)\n if (replace) {\n segment.replaces = [...segments]\n replacement = segment\n }\n segments.push(segment)\n const started = startSegment(\n subscription,\n segment,\n () => this.warnUnoptimized(plan),\n (error) => this.recordOutcome(plan.id, segment, error),\n )\n if (this.states.get(plan.id) !== state) {\n return { changed: false, empty: false, ready: true }\n }\n if (!started)\n throw new Error(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `Subset demand snapshot did not start`\n : codedMessage(159),\n )\n if (replace) {\n subscription.replaceSnapshots(\n segment.where,\n (segment.replaces ?? []).map((replaced) => replaced.where),\n )\n if (segment.ready instanceof Promise) {\n void segment.ready.then(\n () => this.finishReplacement(subscription, plan.id, segment),\n () => {},\n )\n } else {\n this.finishReplacement(subscription, plan.id, segment)\n }\n }\n }\n } finally {\n release()\n }\n if (this.states.get(plan.id) !== state) {\n return { changed: false, empty: false, ready: true }\n }\n\n const pending = (replacement ? [replacement] : state.segments)\n .filter(\n (segment) =>\n segment.state === `pending` && intersects(segment.keys, nextKeys),\n )\n .map((segment) => segment.ready)\n .filter((ready) => ready instanceof Promise)\n return {\n changed: true,\n empty: false,\n ready: pending.length > 0 ? Promise.all(pending) : true,\n }\n }\n\n // The outcome of a segment the plan still holds decides its needed keys: a\n // success (no `error`) loaded them, and a failure leaves them missing,\n // unless a later segment loads the key (law 10: a key's latest load\n // decides it). A released segment's outcome decides nothing.\n private recordOutcome(\n planId: string,\n segment: DemandSegment,\n error?: Error,\n ): void {\n const state = this.states.get(planId)\n if (!state?.segments.includes(segment)) return\n const later = state.segments.slice(state.segments.indexOf(segment) + 1)\n for (const key of segment.keys.keys())\n if (\n state.keys.has(key) &&\n !(error && later.some((loader) => loader.keys.has(key)))\n )\n this.onKeyOutcome?.(planId, key, error)\n }\n\n clear(): void {\n for (const state of this.states.values()) {\n for (const segment of state.segments) segment.abortController.abort()\n }\n this.states.clear()\n this.warnedPlans.clear()\n this.valueIdentity = createValueIdentity()\n }\n\n hasPendingDemand(planId: string): boolean {\n return (\n this.states\n .get(planId)\n ?.segments.some((segment) => segment.state === `pending`) ?? false\n )\n }\n\n private finishReplacement(\n subscription: CollectionSubscription,\n planId: string,\n replacement: DemandSegment,\n ): void {\n const state = this.states.get(planId)\n if (\n !state?.segments.includes(replacement) ||\n !equalKeySets(replacement.keys, state.keys)\n ) {\n releaseSegments(subscription, [replacement])\n return\n }\n const replaced = replacement.replaces ?? []\n replacement.replaces = undefined\n state.segments = [replacement]\n releaseSegments(subscription, replaced)\n }\n\n private warnUnoptimized(plan: LazyDemandPlan): void {\n if (!(devBuild() && process.env.NODE_ENV !== `production`)) return\n if (this.warnedPlans.has(plan.id)) return\n this.warnedPlans.add(plan.id)\n const path = plan.path.join(`.`)\n console.warn(\n `[TanStack DB]${plan.collectionId ? ` [${plan.collectionId}]` : ``} Join requires an index on \"${path}\" for efficient loading. ` +\n `Falling back to scanning local data. ` +\n `Consider creating an index on the collection with collection.createIndex((row) => row.${path}) ` +\n `or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.`,\n )\n }\n}\n\n/** An ordered filter waits only for demand on its joined source. */\nexport function hasPendingJoinedWork(\n joinedSourceId: string,\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks>,\n subscriptions: Record<string, CollectionSubscription>,\n isPendingPlan: (planId: string) => boolean,\n): boolean {\n return (\n (lazySourcesCallbacks[joinedSourceId]?.plans?.some((plan) =>\n isPendingPlan(plan.id),\n ) ??\n false) ||\n subscriptions[joinedSourceId]?.status === `loadingSubset`\n )\n}\n\nfunction canonicalizeKeys(\n keys: Set<unknown>,\n valueIdentity: ValueIdentity,\n): Map<string, unknown> {\n return new Map(\n [...keys].map((key) => [valueIdentity.serializeEquality(key), key]),\n )\n}\n\nfunction equalKeySets(\n left: Map<string, unknown>,\n right: Map<string, unknown>,\n): boolean {\n return (\n left.size === right.size && [...left.keys()].every((key) => right.has(key))\n )\n}\n\nfunction intersects(\n left: Map<string, unknown>,\n right: Map<string, unknown>,\n): boolean {\n return [...left.keys()].some((key) => right.has(key))\n}\n\nfunction createSegment(\n plan: LazyDemandPlan,\n keys: Map<string, unknown>,\n): DemandSegment {\n const where = inArray(new PropRef(plan.path), [...keys.values()])\n return {\n keys,\n where,\n abortController: new AbortController(),\n ready: true,\n state: `starting`,\n }\n}\n\n// An abort ends the segment's load: its outcome decides nothing. A replay\n// that aborted it covers its keys, so the load's consumers wait for that\n// replay instead. While a truncate's or a reload's replay is judged, its\n// verdict decides the outcome of every load that settles, so the load\n// decides no request error itself and does not fail the query's first load.\nfunction startSegment(\n subscription: CollectionSubscription,\n segment: DemandSegment,\n onUnoptimized: () => void,\n onOutcome: (error?: Error) => void,\n): boolean {\n let observed = false\n const requested = subscription.requestSnapshot({\n where: segment.where,\n signal: segment.abortController.signal,\n trackLoadSubsetPromise: false,\n onUnoptimized,\n onLoadSubsetResult: (result, options) => {\n observed = true\n if (!(result instanceof Promise)) {\n segment.state = `settled`\n return\n }\n segment.state = `pending`\n const settle = (failure?: {\n error: unknown\n }): Promise<void> | undefined => {\n if (options.signal?.aborted)\n return replayed(subscription).then(() => {\n segment.state = `settled`\n })\n segment.state = failure ? `failed` : `settled`\n if (subscription.pendingTruncateReplacement) return undefined\n const error = failure && normalizeError(failure.error)\n onOutcome(error)\n if (error) throw error\n return undefined\n }\n segment.ready = result.then(\n () => settle(),\n (error: unknown) => settle({ error }),\n )\n },\n })\n return requested && observed\n}\n\n// Settles once no truncate's or reload's replay is pending, also when a\n// newer one replaces the pending one.\nfunction replayed(subscription: CollectionSubscription): Promise<void> {\n const replay = subscription.pendingTruncateReplacement\n if (!replay) return Promise.resolve()\n const next = () => replayed(subscription)\n return replay.then(next, next)\n}\n\n// One demand change: the subscription releases every segment even if\n// subscriber code that a release runs throws, then reports the first error.\nfunction releaseSegments(\n subscription: CollectionSubscription,\n segments: Array<DemandSegment>,\n): void {\n if (segments.length === 0) return\n for (const segment of segments) segment.abortController.abort()\n subscription.releaseSnapshot(...segments.map((segment) => segment.where))\n}\n"],"names":["onKeyOutcome","warnedPlans","createValueIdentity","warnUnoptimized","error","recordOutcome","devBuild","codedMessage","finishReplacement","inArray","PropRef","normalizeError"],"mappings":"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"}