UNPKG

@tanstack/react-db

Version:

React integration for @tanstack/db

208 lines (207 loc) • 8.09 kB
"use client"; "use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const react = require("react"); const db = require("@tanstack/db"); const useLiveQuery = require("./useLiveQuery.cjs"); const sourceIdBindings = require("./source-id-bindings.cjs"); const unscopedClient = {}; const suspenseCollectionsByClient = /* @__PURE__ */ new WeakMap(); function getSourceQualifiedSuspenseKey(preparedValue, queryHash, client) { const sourceIds = sourceIdBindings.getPreparedSources( preparedValue, client, `useLiveSuspenseQuery` ).map(db.getSourceObjectToken); const { query: _query, pool: _pool, ...options } = preparedValue?.query ? preparedValue : {}; options.autoIndex ??= `off`; for (const key in options) { const value = options[key]; if (value === void 0) delete options[key]; else if (value && (typeof value === `object` || typeof value === `function`)) options[key] = db.getSourceObjectToken(value); } return db.getStableValueHash([sourceIds, options, queryHash]); } class SuspenseBinding { constructor() { this.ready = false; } get needsOwnCollection() { return this.entry !== void 0 && this.entry.owner !== this; } resolve(input, hash, client, compile) { const scope = client ?? unscopedClient; let cache = suspenseCollectionsByClient.get(scope); if (!cache) { cache = /* @__PURE__ */ new Map(); suspenseCollectionsByClient.set(scope, cache); } const owned = this.entry?.owner === this; const key = owned ? void 0 : getSourceQualifiedSuspenseKey(input, hash, client); const cached = key === void 0 ? void 0 : cache.get(key); if (cached) return (this.entry = cached).collection; const windowOwner = owned ? this.entry.windowOwner : {}; const collection = compile(owned, windowOwner); if (owned && collection === this.entry.collection) return collection; this.seed = void 0; if (collection && this.entry?.collection !== collection) { const entryKey = key ?? getSourceQualifiedSuspenseKey(input, hash, client); const release = () => { if (cache.get(entryKey) === entry) cache.delete(entryKey); removeStatusListener(); }; const entry = { collection, windowOwner, release }; this.entry = entry; cache.set(entryKey, entry); const removeStatusListener = collection.on( `status:change`, ({ status }) => { if (status === `cleaned-up`) release(); else if (status === `error` || status === `crashed`) setTimeout(release, 0); } ); } return collection; } // The first successful candidate may keep the preload. Other candidates get // private outputs seeded from its completed snapshot. Keep the retry entry // available until commit, since React can discard a successful candidate. forkReady(observer, compile) { if (!this.entry || this.entry.owner === this) return; const resource = this.entry.observer ??= observer; const snapshot = typeof window === `undefined` ? resource.getServerSnapshot(true) : resource.getSnapshot(); if (!snapshot.isReady) return; if (!this.entry.owner) { this.entry.owner = this; return; } const windowOwner = {}; const collection = compile(false, windowOwner); if (!collection) return; this.seed = { ...snapshot, collection }; this.entry = { collection, windowOwner, owner: this, release: this.entry.release }; return collection; } read(snapshot) { if (snapshot.isReady || snapshot.isLoadingError || snapshot.isCrashed) { if (this.seed && snapshot.isLoadingError) this.ready = false; this.seed = void 0; } if (this.seed && snapshot.health !== this.seed.health) { this.seed = { ...this.seed, ...db.getLiveQueryStatusFlags(`ready`, snapshot.health) }; } return this.seed ?? snapshot; } claim() { return this.entry?.release; } } const initialRenderErrors = /* @__PURE__ */ new WeakMap(); function clearInitialRenderError(collection, client) { initialRenderErrors.get(collection)?.delete(client ?? unscopedClient); } function rememberInitialRenderError(collection, client, error, clientQuery) { if (collection.status === `cleaned-up`) return; let entry = initialRenderErrors.get(collection); if (!entry) { entry = /* @__PURE__ */ new WeakMap(); collection.once(`status:cleaned-up`, () => { initialRenderErrors.delete(collection); }); initialRenderErrors.set(collection, entry); } entry.set(client ?? unscopedClient, { error, clientQuery }); } function useLiveSuspenseQuery(configOrQueryOrCollection) { const bindingRef = react.useRef(null); const binding = bindingRef.current ??= new SuspenseBinding(); const { result, client, queryHash, observer, deferredCollections } = useLiveQuery.useLiveQueryImpl(configOrQueryOrCollection, binding); if (!result.isEnabled) { throw new Error( `useLiveSuspenseQuery does not support disabled queries (callback returned undefined/null). The Suspense pattern requires data to always be defined (T, not T | undefined). Solutions: 1) Use conditional rendering - don't render the component until the condition is met. 2) Use useLiveQuery instead, which supports disabled queries with the 'isEnabled' flag.` ); } const collection = result.collection; try { if (binding.collection !== collection) { binding.promise = void 0; binding.collection = collection; binding.ready = false; } const collectionStatus = collection.status; if (result.isReady) { binding.ready = true; binding.promise = void 0; clearInitialRenderError(collection, client); } const observerError = observer.getError(); const clientQuery = client && queryHash ? client._getLiveQuery(queryHash) : void 0; if (!binding.ready && (observerError !== void 0 || clientQuery?.status === `error`)) { throw observerError === void 0 ? clientQuery?.error : observerError; } const initialRenderError = initialRenderErrors.get(collection)?.get(client ?? unscopedClient); if (initialRenderError?.clientQuery !== void 0 && initialRenderError.clientQuery !== clientQuery) { clearInitialRenderError(collection, client); } else if (initialRenderError && !binding.ready) { throw db.toBoundaryError(initialRenderError.error); } if (collectionStatus === `crashed` || collectionStatus === `error` && !binding.ready) { throw db.toBoundaryError(collection.health.error?.cause); } if (!binding.ready && (result.isLoading || result.isIdle || result.isLoadingError || result.isCrashed)) { if (!binding.promise) { let active = true; const stopWatchingCleanup = collection.once(`status:cleaned-up`, () => { active = false; }); let preload; try { preload = observer.preload(); } catch (error) { stopWatchingCleanup(); throw error; } const preloadClientQuery = client && queryHash ? client._getLiveQuery(queryHash) : void 0; binding.promise = preload.catch((error) => { const latestClientQuery = client && queryHash ? client._getLiveQuery(queryHash) : void 0; if (active && latestClientQuery === preloadClientQuery) rememberInitialRenderError( collection, client, error, preloadClientQuery ); throw error; }).finally(stopWatchingCleanup); } throw binding.promise; } return { state: result.state, data: result.data, collection, health: result.health }; } catch (error) { if (error instanceof Promise) throw error; binding.promise = void 0; return db.rejectLiveQueryInput(deferredCollections, error); } } exports.SuspenseBinding = SuspenseBinding; exports.useLiveSuspenseQuery = useLiveSuspenseQuery; //# sourceMappingURL=useLiveSuspenseQuery.cjs.map