@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 15.2 kB
Source Map (JSON)
{"version":3,"file":"live-query-window-binding.cjs","sources":["../../src/live-query-window-binding.ts"],"sourcesContent":["import { codedMessage, devBuild } from './error-message.js'\nimport { UnhashableQueryIRError } from './errors.js'\nimport { getLiveQueryHash } from './live-query-options.js'\nimport { isFailedStatus } from './live-query-primitives.js'\nimport { createLiveQueryCollection } from './query/live-query-collection.js'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryWindowController,\n getLiveQueryWindowCollectionWarning,\n getLiveQueryWindowInputKind,\n liveQueryWindowMatches,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from './live-query-window-controller.js'\nimport type { Collection } from './collection/index.js'\nimport type { DbClient } from './client.js'\nimport type { LiveQueryKey } from './live-query-options.js'\nimport type { LiveQueryWindowController } from './live-query-window-controller.js'\n\n/**\n * Window binding: what one mounted infinite-query hook keeps between updates.\n *\n * Every framework adapter's `useLiveInfiniteQuery` reaches the same decision\n * on each render or reactive rerun. It keeps the binding when nothing changed,\n * replaces only the window controller when the page shape changed, or binds a\n * new window collection when the input changed. A new controller keeps the\n * loaded page count when the old pages still describe the new query.\n * Adapters own only their framework's subscription and disposal of\n * `controller`.\n */\n\n// Live queries created here are cleaned up immediately (0 disables GC).\nconst WINDOW_COLLECTION_GC_TIME_MS = 1\n\n/** Validated infinite-query page shape. */\nexport interface LiveQueryWindowPageShape {\n pageSize: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam: number\n}\n\n/**\n * Read an infinite-query config's page shape. `getNextPageParam` is rejected:\n * the hook widens an ordered live query and has no server cursor to advance.\n */\nexport function readLiveQueryWindowPageShape(config: {\n pageSize?: number\n initialPageParam?: number\n}): LiveQueryWindowPageShape {\n if (`getNextPageParam` in config) {\n throw new Error(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`\n : codedMessage(246),\n )\n }\n return {\n pageSize: normalizeLiveQueryWindowPageSize(config.pageSize),\n initialPageParam: config.initialPageParam ?? 0,\n }\n}\n\n/**\n * When a query input compiles again.\n *\n * - `identity`: when its identity dependencies change by structure, as for a\n * derived query identity or a `queryKey`.\n * - `always`: on every update, as for a reactive rerun, which happens only\n * when something the query reads changed.\n */\nexport type LiveQueryWindowRecompile = `identity` | `always`\n\nexport interface LiveQueryWindowUpdate extends LiveQueryWindowPageShape {\n /**\n * A supplied live-query Collection, or a callback that returns the query.\n * The binding calls the callback only when it compiles a new window\n * collection, except in `always` mode, where it calls it on every update.\n */\n input: unknown\n /** The query input's identity dependencies. */\n dependencies?: ReadonlyArray<unknown>\n /** Context compared by reference, such as the `DbClient` a query binds to. */\n scope?: unknown\n /** Hydration identity for a client-backed window. */\n client?: DbClient\n queryKey?: LiveQueryKey\n recompile: LiveQueryWindowRecompile\n}\n\nexport interface LiveQueryWindowBinding extends LiveQueryWindowPageShape {\n /** The supplied Collection; `undefined` for a query input. */\n supplied: Collection<any, any, any> | undefined\n dependencies: ReadonlyArray<unknown> | undefined\n scope: unknown\n collection: Collection<any, any, any>\n /** The transport identity of this binding's initial query window. */\n queryHash: string | undefined\n /** Whether this binding created `collection` from a query. */\n ownsCollection: boolean\n controller: LiveQueryWindowController<any, any>\n /**\n * A developer hint about a newly bound supplied collection's window, for the\n * adapter to print once. Production builds carry none.\n */\n warning: string | undefined\n}\n\n/**\n * Bind an infinite-query update. Returns `previous` when it still serves the\n * update; otherwise returns a new binding whose controller the adapter\n * subscribes and later disposes.\n *\n * `uncommitted` is a binding built by an earlier update that never took\n * effect, such as a React render that did not commit. When it answers this\n * update, its window collection is reused instead of compiling another.\n */\nexport function bindLiveQueryWindow(\n previous: LiveQueryWindowBinding | null,\n update: LiveQueryWindowUpdate,\n uncommitted?: LiveQueryWindowBinding | null,\n): LiveQueryWindowBinding {\n // A reactive rerun resolves its input first: a getter may return a\n // supplied Collection.\n const resolved =\n update.recompile === `always`\n ? resolveLiveQueryWindowInput<any>(update.input)\n : undefined\n const supplied =\n resolved === undefined\n ? getLiveQueryWindowInputKind(update.input) === `collection`\n ? (update.input as Collection<any, any, any>)\n : undefined\n : resolved.kind === `collection`\n ? resolved.collection\n : undefined\n const inputKind = supplied ? `collection` : `query`\n const previousKind = previous\n ? previous.supplied\n ? `collection`\n : `query`\n : undefined\n // A snapshot: a caller may change its dependency array in place.\n const dependencies = supplied ? undefined : [...(update.dependencies ?? [])]\n\n const change = compareUpdate(previous, update, dependencies)\n const needsNewCollection =\n previous === null ||\n previousKind !== inputKind ||\n (supplied ? previous.supplied !== supplied : change.recompile)\n const pageShapeChanged =\n previous === null ||\n previous.pageSize !== update.pageSize ||\n previous.initialPageParam !== update.initialPageParam\n if (!needsNewCollection && !pageShapeChanged) return previous\n\n const initialPageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: previous !== null,\n previousInputKind: previousKind,\n inputKind,\n sameCollection: supplied !== undefined && previous?.supplied === supplied,\n dependenciesChanged: change.dependenciesChanged,\n dependenciesStructurallyEqual: change.dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n ? Math.max(1, previous!.controller.getSnapshot().pages.length)\n : 1\n // The peek-ahead window for every retained page.\n const requiredLimit = initialPageCount * update.pageSize + 1\n\n let collection = previous?.collection\n let queryHash = previous?.queryHash\n let ownsCollection = previous?.ownsCollection ?? false\n let warning: string | undefined\n if (needsNewCollection) {\n if (\n !supplied &&\n uncommitted?.ownsCollection &&\n !compareUpdate(uncommitted, update, dependencies).recompile &&\n uncommitted.collection.status !== `cleaned-up` &&\n !isFailedStatus(uncommitted.collection.status) &&\n liveQueryWindowMatches(uncommitted.collection, requiredLimit)\n ) {\n // Nothing has taken effect since the earlier update built this\n // collection, and its window is exactly the retained pages, so its first\n // rows are correct. A collection in terminal error or crashed, such as\n // after its source restarted, cannot serve the query again.\n collection = uncommitted.collection\n queryHash = uncommitted.queryHash\n ownsCollection = true\n } else {\n const window = openWindowCollection(\n resolved ?? resolveLiveQueryWindowInput<any>(update.input),\n requiredLimit,\n update,\n )\n collection = window.collection\n queryHash = window.queryHash\n ownsCollection = window.ownsCollection\n warning = window.warning\n }\n }\n\n return {\n supplied,\n dependencies,\n scope: update.scope,\n pageSize: update.pageSize,\n initialPageParam: update.initialPageParam,\n collection: collection!,\n queryHash,\n ownsCollection,\n controller: createLiveQueryWindowController(collection, {\n pageSize: update.pageSize,\n initialPageParam: update.initialPageParam,\n initialPageCount,\n client: update.client,\n queryHash,\n }),\n warning,\n }\n}\n\n/** How an update's query identity differs from a binding's. */\nfunction compareUpdate(\n binding: LiveQueryWindowBinding | null,\n update: LiveQueryWindowUpdate,\n dependencies: ReadonlyArray<unknown> | undefined,\n): {\n recompile: boolean\n dependenciesChanged: boolean\n dependenciesStructurallyEqual: boolean\n} {\n const comparison = compareLiveQueryWindowDependencies(\n binding?.dependencies,\n dependencies ?? [],\n )\n const scopeChanged = binding?.scope !== update.scope\n const dependenciesChanged =\n scopeChanged ||\n (update.recompile === `identity`\n ? !comparison.structurallyEqual\n : comparison.changed)\n return {\n recompile: update.recompile === `always` || dependenciesChanged,\n dependenciesChanged,\n // An identity that changed differs by structure by definition.\n dependenciesStructurallyEqual:\n !scopeChanged &&\n update.recompile !== `identity` &&\n comparison.structurallyEqual,\n }\n}\n\n/**\n * Bind the window collection for a resolved input, starting its sync now when\n * its first published rows are already correct. A created collection's window\n * covers every retained page, so it never publishes fewer rows than the\n * controller's pages; the controller grows it with `setWindow`. A supplied\n * window that the controller must still adjust waits for the subscription.\n */\nfunction openWindowCollection(\n input: ReturnType<typeof resolveLiveQueryWindowInput<any>>,\n requiredLimit: number,\n update: LiveQueryWindowUpdate,\n): {\n collection: Collection<any, any, any>\n queryHash: string | undefined\n ownsCollection: boolean\n warning: string | undefined\n} {\n const prepared =\n input.kind === `collection`\n ? input.collection\n : { query: input.query.limit(requiredLimit).offset(0) }\n let queryHash: string | undefined\n if (update.client) {\n try {\n queryHash = getLiveQueryHash(prepared, update.queryKey)\n } catch (error) {\n if (!(error instanceof UnhashableQueryIRError) || update.queryKey?.length)\n throw error\n }\n }\n if (input.kind === `collection`) {\n const warning = getLiveQueryWindowCollectionWarning(\n input.collection,\n update.pageSize + 1,\n )\n if (liveQueryWindowMatches(input.collection, requiredLimit)) {\n input.collection.startSyncImmediate()\n }\n return {\n collection: input.collection,\n queryHash,\n ownsCollection: false,\n warning,\n }\n }\n const collection = createLiveQueryCollection({\n query: (prepared as { query: typeof input.query }).query,\n gcTime: WINDOW_COLLECTION_GC_TIME_MS,\n })\n assertLiveQueryWindowManyResult(collection)\n // Like useLiveQuery, start sync during render once the query is valid. A\n // render reads only rows the sources already hold, and sends no request\n // until the subscription attaches.\n collection.startSyncImmediate()\n return { collection, queryHash, ownsCollection: true, warning: undefined }\n}\n"],"names":["devBuild","codedMessage","normalizeLiveQueryWindowPageSize","resolveLiveQueryWindowInput","getLiveQueryWindowInputKind","shouldPreserveLiveQueryWindowPageCount","isFailedStatus","liveQueryWindowMatches","createLiveQueryWindowController","compareLiveQueryWindowDependencies","getLiveQueryHash","UnhashableQueryIRError","getLiveQueryWindowCollectionWarning","createLiveQueryCollection","assertLiveQueryWindowManyResult"],"mappings":"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"}