UNPKG

@tanstack/react-db

Version:

React integration for @tanstack/db

273 lines (270 loc) • 11.4 kB
"use client"; "use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const react = require("react"); const db = require("@tanstack/db"); const DbProvider = require("./DbProvider.cjs"); const development = require("./development.cjs"); const sourceIdBindings = require("./source-id-bindings.cjs"); const useWindowCommitEffect = typeof window === `undefined` ? react.useEffect : react.useLayoutEffect; const DEFAULT_GC_TIME_MS = 1; const DEFAULT_SUSPENSE_GC_TIME_MS = 5e3; const DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS = 16; const DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD = 10; const DERIVED_IDENTITY_TOTAL_WARN_MS = 50; const warnedDerivedIdentityCallsites = /* @__PURE__ */ new Set(); const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`); function createDerivedIdentityProfiler() { try { if (process.env.NODE_ENV !== `production`) { return { renderCount: 0, totalMs: 0, maxMs: 0, warned: false }; } } catch { } return void 0; } function getCurrentTime() { return typeof performance !== `undefined` && typeof performance.now === `function` ? performance.now() : Date.now(); } function getWarningCallsite(stackIndex) { const stack = new Error().stack ?? `unknown`; const lines = stack.split(` `); const userFrame = lines.slice(1).find( (line) => !line.includes(`useLiveQuery.ts`) && !line.includes(`useLiveSuspenseQuery.ts`) && !line.includes(`useLiveInfiniteQuery.ts`) ); return userFrame?.trim() ?? lines[stackIndex]?.trim() ?? stack; } function warnDerivedIdentityHotPath(profiler, durationMs) { if (profiler.warned || !(development.shouldWarnInDevelopment(`TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS`) && process.env.NODE_ENV !== `production`)) { return; } const isSlowSingleRender = durationMs >= DERIVED_IDENTITY_SINGLE_RENDER_WARN_MS; const isHotRenderPath = profiler.renderCount >= DERIVED_IDENTITY_RENDER_COUNT_WARN_THRESHOLD && profiler.totalMs >= DERIVED_IDENTITY_TOTAL_WARN_MS; if (!isSlowSingleRender && !isHotRenderPath) { return; } const callsite = getWarningCallsite(5); if (warnedDerivedIdentityCallsites.has(callsite)) { profiler.warned = true; return; } warnedDerivedIdentityCallsites.add(callsite); profiler.warned = true; const reason = isSlowSingleRender ? `one render took ${durationMs.toFixed(1)}ms` : `${profiler.renderCount} renders took ${profiler.totalMs.toFixed(1)}ms`; console.warn( `[useLiveQuery] Deriving live query identity from structured query IR is running on a hot render path (${reason}, max ${profiler.maxMs.toFixed(1)}ms). Provide an explicit queryKey to skip rebuilding and hashing the IR on every render: useLiveQuery({ queryKey: [...], query }).` ); } function getIdentityScope(claims, dbClient, hookName) { return { claims, client: dbClient, bindingName: hookName, onUnboundDescriptor: sourceIdBindings.getUnboundDescriptorReporter(dbClient, hookName) }; } function prepareDerivedQuery(value, profiler, scope, deferredCollections) { const shouldProfile = profiler !== void 0 && /* @__PURE__ */ development.shouldWarnInDevelopment( `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS` ); const start = shouldProfile && process.env.NODE_ENV !== `production` ? getCurrentTime() : 0; const preparedValue = db.prepareLiveQueryValue( value, scope.client, deferredCollections ); try { return { value: preparedValue, identityDeps: [`derived`, db.identifyLiveQuery(preparedValue, scope)] }; } finally { if (shouldProfile && process.env.NODE_ENV !== `production`) { const durationMs = getCurrentTime() - start; profiler.renderCount += 1; profiler.totalMs += durationMs; profiler.maxMs = Math.max(profiler.maxMs, durationMs); warnDerivedIdentityHotPath(profiler, durationMs); } } } function useLiveQuery(configOrQueryOrCollection) { return useLiveQueryImpl(configOrQueryOrCollection); } function useLiveQueryImpl(configOrQueryOrCollection, suspense) { const forSuspense = suspense !== void 0; const contextDbClient = DbProvider.useOptionalDbClient(); const inputIsCollection = db.isCollection(configOrQueryOrCollection); const dbClient = (inputIsCollection || typeof configOrQueryOrCollection !== `object` ? void 0 : configOrQueryOrCollection?.client) ?? contextDbClient; const instanceRef = react.useRef(null); const instance = instanceRef.current ??= { collection: null, identityDeps: null, derivedIdentityProfiler: createDerivedIdentityProfiler(), deferredCollections: /* @__PURE__ */ new Set(), sourceIds: db.createLiveQuerySourceIdClaims() }; const resumeDeferredCollections = () => { instance.cancelReclaimRelease?.(); instance.cancelReclaimRelease = void 0; db.resumeDeferredLiveQueryCollections(instance.deferredCollections); }; try { const queryKey = !inputIsCollection ? db.getLiveQueryKey(configOrQueryOrCollection) : void 0; let preparedQueryValue = unpreparedQueryValue; let identityDeps; if (queryKey) { identityDeps = queryKey; } else if (inputIsCollection) { identityDeps = []; } else { const preparation = prepareDerivedQuery( configOrQueryOrCollection, instance.derivedIdentityProfiler, getIdentityScope(instance.sourceIds, dbClient, `useLiveQuery`), instance.deferredCollections ); preparedQueryValue = preparation.value; identityDeps = preparation.identityDeps; } let needsNewCollection = suspense?.needsOwnCollection || !instance.collection || (inputIsCollection ? instance.config !== configOrQueryOrCollection : instance.client !== dbClient || !db.deepEquals(instance.identityDeps, identityDeps)); if (needsNewCollection && !inputIsCollection && preparedQueryValue === unpreparedQueryValue) { preparedQueryValue = db.prepareLiveQueryValue( configOrQueryOrCollection, dbClient, instance.deferredCollections ); } let queryHash = instance.queryHash; let commitWindow; let compile; if (needsNewCollection) { queryHash = queryKey ? db.getLiveQueryHash(preparedQueryValue, queryKey) : db.getStableValueHash( inputIsCollection ? [`collection`, configOrQueryOrCollection.id] : identityDeps, `queryKey` ); if (inputIsCollection) { if (/* @__PURE__ */ development.shouldWarnInDevelopment( `TANSTACK_DB_DISABLE_QUERY_IDENTITY_WARNINGS` ) && process.env.NODE_ENV !== `production` && configOrQueryOrCollection.config?.syncMode === `on-demand`) { console.warn( `[useLiveQuery] Warning: Passing a collection with syncMode "on-demand" directly to useLiveQuery will not load any data. In on-demand mode, data is only loaded when queries with predicates request it. Instead, use a query builder function: const { data } = useLiveQuery({ query: (q) => q.from({ c: myCollection }).select(({ c }) => c) }) Or switch to syncMode "eager" if you want all data to sync automatically.` ); } configOrQueryOrCollection.startSyncImmediate(); instance.collection = configOrQueryOrCollection; } else { compile = (ownsCollection, windowOwner) => { if (!dbClient && queryKey !== void 0) { sourceIdBindings.getPreparedSources(preparedQueryValue, dbClient, `useLiveQuery`); } const collection = db.resolveLiveQueryValue(preparedQueryValue, { gcTime: forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS, // Suspense and client observers have distinct preload/snapshot // policies, so neither uses equality-partition pooling. pool: forSuspense || dbClient ? false : void 0, previousCollection: ownsCollection && instance.client === dbClient ? instance.collection : null, windowOwner, onWindowChange: (move) => { commitWindow = move; }, // Without a DbClient, Suspense starts it through a preload below. startSync: !forSuspense || dbClient !== void 0 }); if (collection && !db.isCollection(preparedQueryValue)) { instance.cancelReclaimRelease?.(); instance.cancelReclaimRelease = db.releaseDeferredOnReclaim( collection, instance.deferredCollections ); } return collection; }; instance.collection = suspense ? suspense.resolve(preparedQueryValue, queryHash, dbClient, compile) : compile(true, instance); if (!commitWindow) instance.identityDeps = [...identityDeps]; } instance.config = configOrQueryOrCollection; instance.client = dbClient; if (!commitWindow) instance.queryHash = queryHash; } if (commitWindow && !dbClient) needsNewCollection = false; let observer = instance.observer; let subscribe = instance.subscribe; const watchCollection = () => { observer = db.createLiveQueryObserver(instance.collection, { mode: `wholesale`, client: dbClient, queryHash, onPreload: resumeDeferredCollections, suspendUntilReady: forSuspense }); subscribe = (onStoreChange) => { const unsubscribe = observer.subscribe(onStoreChange); resumeDeferredCollections(); return unsubscribe; }; }; if (needsNewCollection) watchCollection(); useWindowCommitEffect(() => { if (commitWindow) { instance.identityDeps = [...identityDeps]; instance.queryHash = queryHash; instance.observer = observer; instance.subscribe = subscribe; commitWindow(); } claim?.(); }); if (forSuspense && !dbClient && (instance.collection?.status === `idle` || instance.collection?.status === `cleaned-up`)) { observer.preload().catch(() => { }); } const privateCollection = compile && suspense?.forkReady(observer, compile); if (privateCollection) { instance.collection = privateCollection; watchCollection(); } if (!commitWindow) { instance.observer = observer; instance.subscribe = subscribe; } const returned = react.useSyncExternalStore( subscribe, () => { const snapshot = observer.getSnapshot(); return suspense ? suspense.read(snapshot) : snapshot; }, () => { const snapshot = observer.getServerSnapshot(forSuspense); return suspense ? suspense.read(snapshot) : snapshot; } ); const claim = returned.isReady ? suspense?.claim() : void 0; if (forSuspense) { return { result: returned, client: dbClient, queryHash, observer, deferredCollections: instance.deferredCollections }; } return returned; } catch (error) { return db.rejectLiveQueryInput(instance.deferredCollections, error); } } Object.defineProperty(exports, "prepareQueryValue", { enumerable: true, get: () => db.prepareLiveQueryValue }); exports.createDerivedIdentityProfiler = createDerivedIdentityProfiler; exports.getIdentityScope = getIdentityScope; exports.prepareDerivedQuery = prepareDerivedQuery; exports.useLiveQuery = useLiveQuery; exports.useLiveQueryImpl = useLiveQueryImpl; //# sourceMappingURL=useLiveQuery.cjs.map