UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

234 lines (233 loc) • 8.82 kB
import { getLiveQueryRowOptions } from "./live-query-options.js"; import { devBuild, codedMessage } from "./error-message.js"; import { getBuilderFromConfig } from "./query/live/collection-registry.js"; import { getPooledLiveQuerySource } from "./query/pooled-source.js"; import { isFailedStatus } from "./live-query-primitives.js"; class LiveQueryObserverClient { constructor(client, collection, queryHash, usesSettledHydration, host) { this.client = client; this.collection = collection; this.queryHash = queryHash; this.gU = usesSettledHydration; this.host = host; this.failed = false; this.authoritative = false; this.dO = false; } get seed() { return this.authoritative ? void 0 : this.a$; } get query() { return this.queryHash ? this.client._getLiveQuery(this.queryHash) : void 0; } sync(forServerSnapshot = false) { if (!this.queryHash || this.authoritative) return false; this.client._waitForSsrLiveQuery(this.queryHash); const query = this.client._getLiveQuery(this.queryHash, forServerSnapshot); if (!query) return false; if (this.host.isAttached() && !this.a$ && this.collection?.status === `ready` && !this.collection.isLoadingSubset) { return this.markAuthoritative(query.dehydratedAt); } if (query.status === `error` && (this.gU || !forServerSnapshot || !query.initialSnapshot)) { const changed = !this.failed || this.error !== query.error; this.error = query.error; this.failed = true; if (changed) this.host.dirty(); return changed; } const snapshot = !this.gU && query.initialSnapshot || query.snapshot; if (!snapshot || this.a$?.snapshot === snapshot) return false; this.a$ = { dehydratedAt: query.dehydratedAt, snapshot, entries: snapshot.rows.map((row) => [row.key, row.value]) }; this.error = void 0; this.failed = false; this.host.dirty(); return true; } getServerSnapshot(preloadRequested) { preloadRequested &&= this.client._isSsrStreamingEnabled(); if (typeof window !== `undefined` && !this.authoritative && this.queryHash) { const waiting = this.client._waitForSsrLiveQuery(this.queryHash, true); if (waiting) throw waiting; const query = this.client._getLiveQuery(this.queryHash, true); const snapshot2 = this.gU ? query?.snapshot : query?.initialSnapshot ?? query?.snapshot; if (query?.status === `pending` && !snapshot2) { if (this.gU) void this.host.observer.preload().catch(() => { }); throw query.promise; } } if (preloadRequested) { void this.host.observer.preload().catch(() => { }); } if (typeof window === `undefined`) this.assertSources(); const snapshot = this.host.read(true); const collection = this.collection; if (!collection || this.seed) return snapshot; if (typeof window !== `undefined`) return snapshot; if (preloadRequested && !snapshot.isReady) return snapshot; const { sources } = this.gx(); const entries = this.host.entries(); if (!this.queryHash || !preloadRequested && (entries.length > 0 || snapshot.isReady) && !(sources ?? [collection]).every( (source) => this.client._hasSsrCollectionSnapshot(source) )) { throw new Error( devBuild() && process.env.NODE_ENV !== `production` ? `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.` : codedMessage(252) ); } this.client._captureServerQuery( this.queryHash, { rows: entries.map(([key, value]) => ({ key, value })), ...snapshot.isReady ? {} : { status: snapshot.isLoadingError || snapshot.isCrashed ? `error` : `loading` } }, sources, getLiveQueryRowOptions(collection) ); return this.host.read(true); } fo(previous, next) { const previousByKey = new Map(previous); const nextByKey = new Map(next); const changes = []; for (const [key, value] of previous) { if (!nextByKey.has(key)) changes.push({ type: `delete`, key, value }); } for (const [key, value] of next) { const previousValue = previousByKey.get(key); if (previousValue === void 0) changes.push({ type: `insert`, key, value }); else if (previousValue !== value) changes.push({ type: `update`, key, value, previousValue }); } return changes; } markAuthoritative(dehydratedAt) { if (this.authoritative) return false; dehydratedAt ??= this.query?.dehydratedAt; const changed = this.failed; this.error = void 0; this.failed = false; this.authoritative = true; if (dehydratedAt !== void 0 && this.queryHash) this.client._consumeLiveQueryResult(this.queryHash, dehydratedAt); if (changed) this.host.dirty(); return changed; } scheduleHandoff() { if (this.dO) return; this.dO = true; queueMicrotask(() => { this.dO = false; this.handoff(); }); } handoff() { const collection = this.collection; if (this.host.isDisposed() || !this.host.isAttached() || !collection || !this.seed || // A failure ends the sync run whose result the seed stood in for. !isFailedStatus(collection.status) && (collection.status !== `ready` || collection.isLoadingSubset)) return; const previous = this.seed.entries; const dehydratedAt = this.seed.dehydratedAt; const entries = Array.from(collection.entries()); this.a$ = void 0; this.markAuthoritative(dehydratedAt); this.host.publish(this.fo(previous, entries), entries); } assertSources() { const { sources } = this.gx(); if (this.queryHash && sources) this.client._assertLiveQuerySources( this.queryHash, sources, getLiveQueryRowOptions(this.collection) ); } subscribe() { if (!this.queryHash) return () => { }; return this.client.subscribe((event) => { if (event.type === `liveQueryStreamError` || event.query.queryHash !== this.queryHash) return; const previous = this.host.entries(); if (this.sync()) this.host.publish(this.fo(previous, this.host.entries())); }); } registerResource() { const config = this.collection?.config; if (this.a9 || !this.client._isSsrServerCleanupEnabled() || !config || !getBuilderFromConfig(config)) return; this.a9 = this.client._registerLiveQueryResource( this.host.observer, async () => { this.host.observer.dispose(); await this.collection?.cleanup({ dependents: true }); } ); } takeResourceRelease() { const release = this.a9; this.a9 = void 0; return release; } gx() { const collection = this.collection; const config = collection?.config; const pooledSource = collection && getPooledLiveQuerySource(collection); const sources = config ? getBuilderFromConfig(config)?.getSourceCollections() : pooledSource ? [pooledSource] : void 0; return { sources, pooledSource: pooledSource || void 0 }; } preload() { if (this.preloadPromise) return this.preloadPromise; const { sources, pooledSource } = this.gx(); if (this.queryHash) { const query = this.query; try { this.client._waitForSsrLiveQuery(this.queryHash); if (sources) this.client._assertLiveQuerySources( this.queryHash, sources, getLiveQueryRowOptions(this.collection) ); if (query?.status === `pending` || query?.status === `success` && query.snapshot?.status === void 0) { if (pooledSource && this.collection) return Promise.all([query.promise, this.collection.preload()]).then( () => { } ); this.collection?._markPreload(); return query.status === `pending` ? query.promise : Promise.resolve(); } } catch (error) { return Promise.reject(error); } } this.registerResource(); const collectionPromise = this.host.preload(); const promise = this.client._isSsrStreamingEnabled() && this.queryHash ? this.client._registerLiveQuery( this.queryHash, collectionPromise.then(() => this.host.observer.dehydrate()), sources, getLiveQueryRowOptions(this.collection) ) : collectionPromise; this.preloadPromise = promise; const clear = () => { if (this.preloadPromise === promise) this.preloadPromise = void 0; }; void promise.then(clear, clear); return promise; } } export { LiveQueryObserverClient }; //# sourceMappingURL=live-query-observer-client.js.map