@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 18.3 kB
Source Map (JSON)
{"version":3,"file":"live-query-observer-client.cjs","sources":["../../src/live-query-observer-client.ts"],"sourcesContent":["import { getLiveQueryRowOptions } from './live-query-options.js'\nimport { codedMessage, devBuild } from './error-message.js'\nimport { getBuilderFromConfig } from './query/live/collection-registry.js'\nimport { getPooledLiveQuerySource } from './query/pooled-source.js'\nimport { isFailedStatus } from './live-query-primitives.js'\nimport type { Collection } from './collection/index.js'\nimport type { DbClient, DehydratedLiveQueryResult } from './client.js'\nimport type {\n LiveQueryObserver,\n LiveQuerySnapshot,\n} from './live-query-observer.js'\nimport type { ChangeMessage } from './types.js'\n\n/**\n * DbClient supplies transport behavior to the shared observer. Keeping this\n * implementation behind the client means ordinary framework queries do not\n * import server admission, hydration reconciliation, or streaming machinery.\n */\nexport interface LiveQueryObserverClientHost<\n T extends object,\n TKey extends string | number,\n> {\n observer: LiveQueryObserver<T, TKey>\n read: (forServerSnapshot: boolean) => LiveQuerySnapshot<T, TKey>\n entries: () => Array<[TKey, T]>\n isAttached: () => boolean\n isDisposed: () => boolean\n dirty: () => void\n publish: (\n changes: Array<ChangeMessage<T, TKey>>,\n entries?: Array<[TKey, T]>,\n ) => void\n preload: () => Promise<void>\n}\n\nexport class LiveQueryObserverClient<\n T extends object,\n TKey extends string | number,\n> {\n private hydrationSeed:\n | {\n dehydratedAt: number\n entries: Array<[TKey, T]>\n snapshot: DehydratedLiveQueryResult\n }\n | undefined\n error: unknown\n failed = false\n private authoritative = false\n private handoffScheduled = false\n private unregisterResource: (() => void) | undefined\n private preloadPromise: Promise<void> | undefined\n\n constructor(\n private readonly client: DbClient,\n private readonly collection: Collection<T, TKey, any> | null,\n private readonly queryHash: string | undefined,\n private readonly usesSettledHydration: boolean,\n private readonly host: LiveQueryObserverClientHost<T, TKey>,\n ) {}\n\n get seed() {\n return this.authoritative ? undefined : this.hydrationSeed\n }\n\n get query() {\n return this.queryHash\n ? this.client._getLiveQuery(this.queryHash)\n : undefined\n }\n\n sync(forServerSnapshot = false): boolean {\n if (!this.queryHash || this.authoritative) return false\n this.client._waitForSsrLiveQuery(this.queryHash)\n const query = this.client._getLiveQuery(this.queryHash, forServerSnapshot)\n if (!query) return false\n if (\n this.host.isAttached() &&\n !this.hydrationSeed &&\n this.collection?.status === `ready` &&\n !this.collection.isLoadingSubset\n ) {\n return this.markAuthoritative(query.dehydratedAt)\n }\n if (\n query.status === `error` &&\n (this.usesSettledHydration ||\n !forServerSnapshot ||\n !query.initialSnapshot)\n ) {\n const changed = !this.failed || this.error !== query.error\n this.error = query.error\n this.failed = true\n if (changed) this.host.dirty()\n return changed\n }\n const snapshot =\n (!this.usesSettledHydration && query.initialSnapshot) || query.snapshot\n if (!snapshot || this.hydrationSeed?.snapshot === snapshot) return false\n this.hydrationSeed = {\n dehydratedAt: query.dehydratedAt,\n snapshot,\n entries: snapshot.rows.map((row) => [row.key as TKey, row.value as T]),\n }\n this.error = undefined\n this.failed = false\n this.host.dirty()\n return true\n }\n\n getServerSnapshot(preloadRequested: boolean): LiveQuerySnapshot<T, TKey> {\n preloadRequested &&= this.client._isSsrStreamingEnabled()\n if (\n typeof window !== `undefined` &&\n !this.authoritative &&\n this.queryHash\n ) {\n const waiting = this.client._waitForSsrLiveQuery(this.queryHash, true)\n if (waiting) throw waiting\n const query = this.client._getLiveQuery(this.queryHash, true)\n const snapshot = this.usesSettledHydration\n ? query?.snapshot\n : (query?.initialSnapshot ?? query?.snapshot)\n // A later boundary may hydrate before its result promise arrives.\n // Local rows cannot substitute for the missing server-rendered view.\n if (query?.status === `pending` && !snapshot) {\n if (this.usesSettledHydration)\n void this.host.observer.preload().catch(() => {})\n throw query.promise\n }\n }\n if (preloadRequested) {\n void this.host.observer.preload().catch(() => {})\n }\n if (typeof window === `undefined`) this.assertSources()\n const snapshot = this.host.read(true)\n const collection = this.collection\n if (!collection || this.seed) return snapshot\n // React also calls this getter in the browser during hydration. Only\n // server reads perform admission; transported seeds win over local activity.\n if (typeof window !== `undefined`) return snapshot\n // A suspended component has not rendered this loading view. Its settled\n // stream will supply the first rendered result.\n if (preloadRequested && !snapshot.isReady) return snapshot\n\n const { sources } = this.getSourceInfo()\n const entries = this.host.entries()\n if (\n !this.queryHash ||\n (!preloadRequested &&\n (entries.length > 0 || snapshot.isReady) &&\n !(sources ?? [collection]).every((source) =>\n this.client._hasSsrCollectionSnapshot(source),\n ))\n ) {\n throw new Error(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `SSR cannot render server-only live-query rows or readiness. Await DbClient.preloadLiveQuery() for this query, or explicitly preload its source Collections for hydration. Opaque queries require an explicit serializable queryKey.`\n : codedMessage(252),\n )\n }\n // Capture at render, rather than sampling mutable source rows at dehydrate.\n // Loading views transfer rows without answering a later data preload.\n this.client._captureServerQuery(\n this.queryHash,\n {\n rows: entries.map(([key, value]) => ({ key, value })),\n ...(snapshot.isReady\n ? {}\n : {\n status:\n snapshot.isLoadingError || snapshot.isCrashed\n ? (`error` as const)\n : (`loading` as const),\n }),\n },\n sources,\n getLiveQueryRowOptions(collection),\n )\n return this.host.read(true)\n }\n\n private diffEntries(\n previous: Array<[TKey, T]>,\n next: Array<[TKey, T]>,\n ): Array<ChangeMessage<T, TKey>> {\n const previousByKey = new Map(previous)\n const nextByKey = new Map(next)\n const changes: Array<ChangeMessage<T, TKey>> = []\n for (const [key, value] of previous) {\n if (!nextByKey.has(key)) changes.push({ type: `delete`, key, value })\n }\n for (const [key, value] of next) {\n const previousValue = previousByKey.get(key)\n if (previousValue === undefined)\n changes.push({ type: `insert`, key, value })\n else if (previousValue !== value)\n changes.push({ type: `update`, key, value, previousValue })\n }\n return changes\n }\n\n markAuthoritative(dehydratedAt?: number): boolean {\n if (this.authoritative) return false\n dehydratedAt ??= this.query?.dehydratedAt\n const changed = this.failed\n this.error = undefined\n this.failed = false\n this.authoritative = true\n if (dehydratedAt !== undefined && this.queryHash)\n this.client._consumeLiveQueryResult(this.queryHash, dehydratedAt)\n if (changed) this.host.dirty()\n return changed\n }\n\n scheduleHandoff(): void {\n if (this.handoffScheduled) return\n this.handoffScheduled = true\n queueMicrotask(() => {\n this.handoffScheduled = false\n this.handoff()\n })\n }\n\n handoff(): void {\n const collection = this.collection\n if (\n this.host.isDisposed() ||\n !this.host.isAttached() ||\n !collection ||\n !this.seed ||\n // A failure ends the sync run whose result the seed stood in for.\n (!isFailedStatus(collection.status) &&\n (collection.status !== `ready` || collection.isLoadingSubset))\n )\n return\n const previous = this.seed.entries\n const dehydratedAt = this.seed.dehydratedAt\n const entries = Array.from(collection.entries()) as Array<[TKey, T]>\n this.hydrationSeed = undefined\n this.markAuthoritative(dehydratedAt)\n this.host.publish(this.diffEntries(previous, entries), entries)\n }\n\n assertSources(): void {\n const { sources } = this.getSourceInfo()\n if (this.queryHash && sources)\n this.client._assertLiveQuerySources(\n this.queryHash,\n sources,\n getLiveQueryRowOptions(this.collection),\n )\n }\n\n subscribe(): () => void {\n if (!this.queryHash) return () => {}\n return this.client.subscribe((event) => {\n if (\n event.type === `liveQueryStreamError` ||\n event.query.queryHash !== this.queryHash\n )\n return\n const previous = this.host.entries()\n if (this.sync())\n this.host.publish(this.diffEntries(previous, this.host.entries()))\n })\n }\n\n registerResource(): void {\n const config: object | undefined = this.collection?.config\n if (\n this.unregisterResource ||\n !this.client._isSsrServerCleanupEnabled() ||\n !config ||\n !getBuilderFromConfig(config)\n )\n return\n this.unregisterResource = this.client._registerLiveQueryResource(\n this.host.observer,\n async () => {\n this.host.observer.dispose()\n await this.collection?.cleanup({ dependents: true })\n },\n )\n }\n\n takeResourceRelease(): (() => void) | undefined {\n const release = this.unregisterResource\n this.unregisterResource = undefined\n return release\n }\n\n private getSourceInfo() {\n const collection = this.collection\n const config = collection?.config\n const pooledSource = collection && getPooledLiveQuerySource(collection)\n const sources = config\n ? getBuilderFromConfig(config)?.getSourceCollections()\n : pooledSource\n ? [pooledSource]\n : undefined\n return { sources, pooledSource: pooledSource || undefined }\n }\n\n preload(): Promise<void> {\n if (this.preloadPromise) return this.preloadPromise\n const { sources, pooledSource } = this.getSourceInfo()\n if (this.queryHash) {\n const query = this.query\n try {\n this.client._waitForSsrLiveQuery(this.queryHash)\n if (sources)\n this.client._assertLiveQuerySources(\n this.queryHash,\n sources,\n getLiveQueryRowOptions(this.collection),\n )\n if (\n query?.status === `pending` ||\n (query?.status === `success` && query.snapshot?.status === undefined)\n ) {\n if (pooledSource && this.collection)\n return Promise.all([query.promise, this.collection.preload()]).then(\n () => {},\n )\n // Stream reuse remains explicit demand for the local Collection.\n this.collection?._markPreload()\n return query.status === `pending` ? query.promise : Promise.resolve()\n }\n } catch (error) {\n return Promise.reject(error)\n }\n }\n this.registerResource()\n const collectionPromise = this.host.preload()\n const promise =\n this.client._isSsrStreamingEnabled() && this.queryHash\n ? this.client._registerLiveQuery(\n this.queryHash,\n collectionPromise.then(() => this.host.observer.dehydrate()),\n sources,\n getLiveQueryRowOptions(this.collection),\n )\n : collectionPromise\n this.preloadPromise = promise\n const clear = () => {\n if (this.preloadPromise === promise) this.preloadPromise = undefined\n }\n void promise.then(clear, clear)\n return promise\n }\n}\n"],"names":["usesSettledHydration","handoffScheduled","hydrationSeed","snapshot","getSourceInfo","devBuild","codedMessage","getLiveQueryRowOptions","diffEntries","isFailedStatus","unregisterResource","getBuilderFromConfig","pooledSource","getPooledLiveQuerySource"],"mappings":"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"}