UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 8.23 kB
{"version":3,"file":"subset-demand-controller.cjs","sources":["../../../../src/query/live/subset-demand-controller.ts"],"sourcesContent":["import { serializeValue } from '@tanstack/db-ivm'\nimport { inArray } from '../builder/functions.js'\nimport { PropRef } from '../ir.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type { LazyDemandPlan } 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: Promise<void> | true\n state: `pending` | `settled` | `failed`\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<void> | true\n}\n\n/**\n * Keeps lazy subset requests aligned with the current relation of demanded\n * keys. Additions load only new coverage. Removals release and rebuild only\n * request segments that covered a removed key.\n */\nexport class SubsetDemandController {\n private readonly states = new Map<string, DemandState>()\n private readonly warnedPlans = new Set<string>()\n\n setDemand(\n subscription: CollectionSubscription,\n plan: LazyDemandPlan,\n keys: Set<unknown>,\n ): DemandUpdate {\n const nextKeys = canonicalizeKeys(keys)\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: Array<DemandSegment> = []\n\n for (const segment of previous?.segments ?? []) {\n if (segment.state !== `failed` && intersects(segment.keys, nextKeys)) {\n segments.push(segment)\n continue\n }\n\n segment.abortController.abort()\n subscription.releaseSnapshot(segment.where)\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 if (added.size > 0) {\n segments.push(\n requestSegment(subscription, plan, added, () =>\n this.warnUnoptimized(plan),\n ),\n )\n }\n\n if (nextKeys.size === 0) {\n this.states.delete(plan.id)\n } else {\n this.states.set(plan.id, { keys: nextKeys, segments })\n }\n\n const activeSegments = segments.filter((segment) =>\n intersects(segment.keys, nextKeys),\n )\n const pending = activeSegments\n .map((segment) => segment.ready)\n .filter((ready): ready is Promise<void> => ready instanceof Promise)\n return {\n changed: true,\n empty: nextKeys.size === 0,\n ready:\n pending.length > 0 ? Promise.all(pending).then(() => undefined) : true,\n }\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 }\n\n private warnUnoptimized(plan: LazyDemandPlan): void {\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\nfunction canonicalizeKeys(keys: Set<unknown>): Map<string, unknown> {\n return new Map([...keys].map((key) => [serializeValue(key), key]))\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 requestSegment(\n subscription: CollectionSubscription,\n plan: LazyDemandPlan,\n keys: Map<string, unknown>,\n onUnoptimized: () => void,\n): DemandSegment {\n const where = inArray(new PropRef(plan.path), [...keys.values()])\n const abortController = new AbortController()\n const load = { ready: true as Promise<void> | true }\n subscription.requestSnapshot({\n where,\n signal: abortController.signal,\n trackLoadSubsetPromise: false,\n onUnoptimized,\n onLoadSubsetResult: (result) => {\n load.ready = result\n },\n })\n const ready = load.ready\n const segment: DemandSegment = {\n keys,\n where,\n abortController,\n ready,\n state: ready instanceof Promise ? `pending` : `settled`,\n }\n if (ready instanceof Promise) {\n void ready.then(\n () => {\n segment.state = `settled`\n },\n () => {\n segment.state = `failed`\n },\n )\n }\n return segment\n}\n"],"names":["serializeValue","inArray","PropRef"],"mappings":"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"}