@tanstack/db 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/query/live-query-collection.cjs +4 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/esm/query/live-query-collection.js +4 -0
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/package.json +1 -1
- package/src/query/live-query-collection.ts +7 -0
|
@@ -313,6 +313,10 @@ function liveQueryCollectionOptions(config) {
|
|
|
313
313
|
maybeRunGraph();
|
|
314
314
|
return () => {
|
|
315
315
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
316
|
+
graphCache = void 0;
|
|
317
|
+
inputsCache = void 0;
|
|
318
|
+
pipelineCache = void 0;
|
|
319
|
+
collectionWhereClausesCache = void 0;
|
|
316
320
|
};
|
|
317
321
|
}
|
|
318
322
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@tanstack/db-ivm\"\nimport { createCollection } from \"../collection.js\"\nimport { createFilterFunctionFromExpression } from \"../change-events.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport { convertToBasicExpression } from \"./compiler/expressions.js\"\nimport type { OrderByOptimizationInfo } from \"./compiler/order-by.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { BasicExpression } from \"./ir.js\"\nimport type { LazyCollectionCallbacks } from \"./compiler/joins.js\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every((collection) =>\n collection.isReady()\n )\n }\n\n const allCollectionsReadyOrInitialCommit = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n let collectionWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n\n // Map of collection IDs to functions that load keys for that lazy collection\n const lazyCollectionsCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of collection IDs that are lazy collections\n const lazyCollections = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n const optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> =\n {}\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n\n // Compile the query and get both pipeline and collection WHERE clauses\n ;({\n pipeline: pipelineCache,\n collectionWhereClauses: collectionWhereClausesCache,\n } = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>,\n collections,\n lazyCollectionsCallbacks,\n lazyCollections,\n optimizableOrderByCollections\n ))\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, markReady, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n let subscribedToAllCollections = false\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happend because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n const maybeRunGraph = (callback?: () => boolean) => {\n // We only run the graph if all the collections are ready\n if (\n allCollectionsReadyOrInitialCommit() &&\n subscribedToAllCollections\n ) {\n graph.run()\n const ready = callback?.() ?? true\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n // Mark the collection as ready after the first successful run\n if (ready && allCollectionsReady()) {\n markReady()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Subscribe to all collections, using WHERE clause optimization when available\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n const collectionAlias = findCollectionAlias(collectionId, query)\n const whereClause =\n collectionAlias && collectionWhereClausesCache\n ? collectionWhereClausesCache.get(collectionAlias)\n : undefined\n\n const sendChangesToPipeline = (\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph(callback)\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n const sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean,\n sentKeys: Set<string | number>\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n }\n newChanges.push(newChange)\n }\n\n return sendChangesToPipeline(newChanges)\n }\n\n const loadKeys = (\n keys: Iterable<string | number>,\n sentKeys: Set<string | number>,\n filterFn: (item: object) => boolean\n ) => {\n for (const key of keys) {\n // Only load the key once\n if (sentKeys.has(key)) continue\n\n const value = collection.get(key)\n if (value !== undefined && filterFn(value)) {\n sentKeys.add(key)\n sendChangesToPipeline([{ type: `insert`, key, value }])\n }\n }\n }\n\n const subscribeToAllChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const unsubscribe = collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n // Subscribes to all changes but without the initial state\n // such that we can load keys from the initial state on demand\n // based on the matching keys from the main collection in the join\n const subscribeToMatchingChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n let loadedInitialState = false\n const sentKeys = new Set<string | number>()\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n sendVisibleChangesToPipeline(changes, loadedInitialState, sentKeys)\n }\n\n const unsubscribe = collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n return loadKeys(keys, sentKeys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n lazyCollectionsCallbacks[collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n\n const changes = collection.currentStateAsChanges({\n whereExpression,\n })\n sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n const subscribeToOrderedChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const {\n offset,\n limit,\n comparator,\n index,\n dataNeeded,\n valueExtractorForRawRow,\n } = optimizableOrderByCollections[collectionId]!\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${collectionId}`\n )\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n const loadMoreIfNeeded = () => {\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n loadNextItems(n)\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n return n === 0\n }\n\n // Keep track of the keys we've sent\n // and also the biggest value we've sent so far\n const sentValuesInfo: {\n sentKeys: Set<string | number>\n biggest: any\n } = {\n sentKeys: new Set<string | number>(),\n biggest: undefined,\n }\n\n const sendChangesToPipelineWithTracking = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n const trackedChanges = trackSentValues(\n changes,\n comparator,\n sentValuesInfo\n )\n sendChangesToPipeline(trackedChanges, loadMoreIfNeeded)\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n const loadNextItems = (n: number) => {\n const biggestSentRow = sentValuesInfo.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: collection.get(key) }\n })\n sendChangesToPipelineWithTracking(nextInserts)\n }\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n const filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n sentValuesInfo.biggest\n )\n sendChangesToPipeline(filteredChanges, loadMoreIfNeeded)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n const subscribeToChanges = (\n whereExpression?: BasicExpression<boolean>\n ) => {\n let unsubscribe: () => void\n if (lazyCollections.has(collectionId)) {\n unsubscribe = subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(optimizableOrderByCollections, collectionId)\n ) {\n unsubscribe = subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = subscribeToAllChanges(whereExpression)\n }\n unsubscribeCallbacks.add(unsubscribe)\n }\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n subscribeToChanges()\n }\n })\n\n subscribedToAllCollections = true\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Converts WHERE expressions from the query IR into a BasicExpression for subscribeChanges\n *\n * @param whereExpressions Array of WHERE expressions to convert\n * @param tableAlias The table alias used in the expressions\n * @returns A BasicExpression that can be used with the collection's index system\n */\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\nfunction* trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n tracker: { sentKeys: Set<string | number>; biggest: any }\n) {\n for (const change of changes) {\n tracker.sentKeys.add(change.key)\n\n if (!tracker.biggest) {\n tracker.biggest = change.value\n } else if (comparator(tracker.biggest, change.value) < 0) {\n tracker.biggest = change.value\n }\n\n yield change\n }\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["buildQuery","getQueryIR","collection","D2","compileQuery","output","createFilterFunctionFromExpression","index","convertToBasicExpression","createCollection","MultiSet"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@tanstack/db-ivm\"\nimport { createCollection } from \"../collection.js\"\nimport { createFilterFunctionFromExpression } from \"../change-events.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport { convertToBasicExpression } from \"./compiler/expressions.js\"\nimport type { OrderByOptimizationInfo } from \"./compiler/order-by.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { BasicExpression } from \"./ir.js\"\nimport type { LazyCollectionCallbacks } from \"./compiler/joins.js\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every((collection) =>\n collection.isReady()\n )\n }\n\n const allCollectionsReadyOrInitialCommit = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n let collectionWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n\n // Map of collection IDs to functions that load keys for that lazy collection\n const lazyCollectionsCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of collection IDs that are lazy collections\n const lazyCollections = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n const optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> =\n {}\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n\n // Compile the query and get both pipeline and collection WHERE clauses\n ;({\n pipeline: pipelineCache,\n collectionWhereClauses: collectionWhereClausesCache,\n } = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>,\n collections,\n lazyCollectionsCallbacks,\n lazyCollections,\n optimizableOrderByCollections\n ))\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, markReady, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n let subscribedToAllCollections = false\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happend because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n const maybeRunGraph = (callback?: () => boolean) => {\n // We only run the graph if all the collections are ready\n if (\n allCollectionsReadyOrInitialCommit() &&\n subscribedToAllCollections\n ) {\n graph.run()\n const ready = callback?.() ?? true\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n // Mark the collection as ready after the first successful run\n if (ready && allCollectionsReady()) {\n markReady()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Subscribe to all collections, using WHERE clause optimization when available\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n const collectionAlias = findCollectionAlias(collectionId, query)\n const whereClause =\n collectionAlias && collectionWhereClausesCache\n ? collectionWhereClausesCache.get(collectionAlias)\n : undefined\n\n const sendChangesToPipeline = (\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph(callback)\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n const sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean,\n sentKeys: Set<string | number>\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n }\n newChanges.push(newChange)\n }\n\n return sendChangesToPipeline(newChanges)\n }\n\n const loadKeys = (\n keys: Iterable<string | number>,\n sentKeys: Set<string | number>,\n filterFn: (item: object) => boolean\n ) => {\n for (const key of keys) {\n // Only load the key once\n if (sentKeys.has(key)) continue\n\n const value = collection.get(key)\n if (value !== undefined && filterFn(value)) {\n sentKeys.add(key)\n sendChangesToPipeline([{ type: `insert`, key, value }])\n }\n }\n }\n\n const subscribeToAllChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const unsubscribe = collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n // Subscribes to all changes but without the initial state\n // such that we can load keys from the initial state on demand\n // based on the matching keys from the main collection in the join\n const subscribeToMatchingChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n let loadedInitialState = false\n const sentKeys = new Set<string | number>()\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n sendVisibleChangesToPipeline(changes, loadedInitialState, sentKeys)\n }\n\n const unsubscribe = collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n return loadKeys(keys, sentKeys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n lazyCollectionsCallbacks[collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n\n const changes = collection.currentStateAsChanges({\n whereExpression,\n })\n sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n const subscribeToOrderedChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const {\n offset,\n limit,\n comparator,\n index,\n dataNeeded,\n valueExtractorForRawRow,\n } = optimizableOrderByCollections[collectionId]!\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${collectionId}`\n )\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n const loadMoreIfNeeded = () => {\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n loadNextItems(n)\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n return n === 0\n }\n\n // Keep track of the keys we've sent\n // and also the biggest value we've sent so far\n const sentValuesInfo: {\n sentKeys: Set<string | number>\n biggest: any\n } = {\n sentKeys: new Set<string | number>(),\n biggest: undefined,\n }\n\n const sendChangesToPipelineWithTracking = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n const trackedChanges = trackSentValues(\n changes,\n comparator,\n sentValuesInfo\n )\n sendChangesToPipeline(trackedChanges, loadMoreIfNeeded)\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n const loadNextItems = (n: number) => {\n const biggestSentRow = sentValuesInfo.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: collection.get(key) }\n })\n sendChangesToPipelineWithTracking(nextInserts)\n }\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n const filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n sentValuesInfo.biggest\n )\n sendChangesToPipeline(filteredChanges, loadMoreIfNeeded)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n const subscribeToChanges = (\n whereExpression?: BasicExpression<boolean>\n ) => {\n let unsubscribe: () => void\n if (lazyCollections.has(collectionId)) {\n unsubscribe = subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(optimizableOrderByCollections, collectionId)\n ) {\n unsubscribe = subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = subscribeToAllChanges(whereExpression)\n }\n unsubscribeCallbacks.add(unsubscribe)\n }\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n subscribeToChanges()\n }\n })\n\n subscribedToAllCollections = true\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n\n // Reset caches so a fresh graph/pipeline is compiled on next start\n // This avoids reusing a finalized D2 graph across GC restarts\n graphCache = undefined\n inputsCache = undefined\n pipelineCache = undefined\n collectionWhereClausesCache = undefined\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Converts WHERE expressions from the query IR into a BasicExpression for subscribeChanges\n *\n * @param whereExpressions Array of WHERE expressions to convert\n * @param tableAlias The table alias used in the expressions\n * @returns A BasicExpression that can be used with the collection's index system\n */\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\nfunction* trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n tracker: { sentKeys: Set<string | number>; biggest: any }\n) {\n for (const change of changes) {\n tracker.sentKeys.add(change.key)\n\n if (!tracker.biggest) {\n tracker.biggest = change.value\n } else if (comparator(tracker.biggest, change.value) < 0) {\n tracker.biggest = change.value\n }\n\n yield change\n }\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["buildQuery","getQueryIR","collection","D2","compileQuery","output","createFilterFunctionFromExpression","index","convertToBasicExpression","createCollection","MultiSet"],"mappings":"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"}
|
|
@@ -311,6 +311,10 @@ function liveQueryCollectionOptions(config) {
|
|
|
311
311
|
maybeRunGraph();
|
|
312
312
|
return () => {
|
|
313
313
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
314
|
+
graphCache = void 0;
|
|
315
|
+
inputsCache = void 0;
|
|
316
|
+
pipelineCache = void 0;
|
|
317
|
+
collectionWhereClausesCache = void 0;
|
|
314
318
|
};
|
|
315
319
|
}
|
|
316
320
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-collection.js","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@tanstack/db-ivm\"\nimport { createCollection } from \"../collection.js\"\nimport { createFilterFunctionFromExpression } from \"../change-events.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport { convertToBasicExpression } from \"./compiler/expressions.js\"\nimport type { OrderByOptimizationInfo } from \"./compiler/order-by.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { BasicExpression } from \"./ir.js\"\nimport type { LazyCollectionCallbacks } from \"./compiler/joins.js\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every((collection) =>\n collection.isReady()\n )\n }\n\n const allCollectionsReadyOrInitialCommit = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n let collectionWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n\n // Map of collection IDs to functions that load keys for that lazy collection\n const lazyCollectionsCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of collection IDs that are lazy collections\n const lazyCollections = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n const optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> =\n {}\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n\n // Compile the query and get both pipeline and collection WHERE clauses\n ;({\n pipeline: pipelineCache,\n collectionWhereClauses: collectionWhereClausesCache,\n } = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>,\n collections,\n lazyCollectionsCallbacks,\n lazyCollections,\n optimizableOrderByCollections\n ))\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, markReady, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n let subscribedToAllCollections = false\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happend because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n const maybeRunGraph = (callback?: () => boolean) => {\n // We only run the graph if all the collections are ready\n if (\n allCollectionsReadyOrInitialCommit() &&\n subscribedToAllCollections\n ) {\n graph.run()\n const ready = callback?.() ?? true\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n // Mark the collection as ready after the first successful run\n if (ready && allCollectionsReady()) {\n markReady()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Subscribe to all collections, using WHERE clause optimization when available\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n const collectionAlias = findCollectionAlias(collectionId, query)\n const whereClause =\n collectionAlias && collectionWhereClausesCache\n ? collectionWhereClausesCache.get(collectionAlias)\n : undefined\n\n const sendChangesToPipeline = (\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph(callback)\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n const sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean,\n sentKeys: Set<string | number>\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n }\n newChanges.push(newChange)\n }\n\n return sendChangesToPipeline(newChanges)\n }\n\n const loadKeys = (\n keys: Iterable<string | number>,\n sentKeys: Set<string | number>,\n filterFn: (item: object) => boolean\n ) => {\n for (const key of keys) {\n // Only load the key once\n if (sentKeys.has(key)) continue\n\n const value = collection.get(key)\n if (value !== undefined && filterFn(value)) {\n sentKeys.add(key)\n sendChangesToPipeline([{ type: `insert`, key, value }])\n }\n }\n }\n\n const subscribeToAllChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const unsubscribe = collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n // Subscribes to all changes but without the initial state\n // such that we can load keys from the initial state on demand\n // based on the matching keys from the main collection in the join\n const subscribeToMatchingChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n let loadedInitialState = false\n const sentKeys = new Set<string | number>()\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n sendVisibleChangesToPipeline(changes, loadedInitialState, sentKeys)\n }\n\n const unsubscribe = collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n return loadKeys(keys, sentKeys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n lazyCollectionsCallbacks[collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n\n const changes = collection.currentStateAsChanges({\n whereExpression,\n })\n sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n const subscribeToOrderedChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const {\n offset,\n limit,\n comparator,\n index,\n dataNeeded,\n valueExtractorForRawRow,\n } = optimizableOrderByCollections[collectionId]!\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${collectionId}`\n )\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n const loadMoreIfNeeded = () => {\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n loadNextItems(n)\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n return n === 0\n }\n\n // Keep track of the keys we've sent\n // and also the biggest value we've sent so far\n const sentValuesInfo: {\n sentKeys: Set<string | number>\n biggest: any\n } = {\n sentKeys: new Set<string | number>(),\n biggest: undefined,\n }\n\n const sendChangesToPipelineWithTracking = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n const trackedChanges = trackSentValues(\n changes,\n comparator,\n sentValuesInfo\n )\n sendChangesToPipeline(trackedChanges, loadMoreIfNeeded)\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n const loadNextItems = (n: number) => {\n const biggestSentRow = sentValuesInfo.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: collection.get(key) }\n })\n sendChangesToPipelineWithTracking(nextInserts)\n }\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n const filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n sentValuesInfo.biggest\n )\n sendChangesToPipeline(filteredChanges, loadMoreIfNeeded)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n const subscribeToChanges = (\n whereExpression?: BasicExpression<boolean>\n ) => {\n let unsubscribe: () => void\n if (lazyCollections.has(collectionId)) {\n unsubscribe = subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(optimizableOrderByCollections, collectionId)\n ) {\n unsubscribe = subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = subscribeToAllChanges(whereExpression)\n }\n unsubscribeCallbacks.add(unsubscribe)\n }\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n subscribeToChanges()\n }\n })\n\n subscribedToAllCollections = true\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Converts WHERE expressions from the query IR into a BasicExpression for subscribeChanges\n *\n * @param whereExpressions Array of WHERE expressions to convert\n * @param tableAlias The table alias used in the expressions\n * @returns A BasicExpression that can be used with the collection's index system\n */\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\nfunction* trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n tracker: { sentKeys: Set<string | number>; biggest: any }\n) {\n for (const change of changes) {\n tracker.sentKeys.add(change.key)\n\n if (!tracker.biggest) {\n tracker.biggest = change.value\n } else if (comparator(tracker.biggest, change.value) < 0) {\n tracker.biggest = change.value\n }\n\n yield change\n }\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-query-collection.js","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@tanstack/db-ivm\"\nimport { createCollection } from \"../collection.js\"\nimport { createFilterFunctionFromExpression } from \"../change-events.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport { convertToBasicExpression } from \"./compiler/expressions.js\"\nimport type { OrderByOptimizationInfo } from \"./compiler/order-by.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { BasicExpression } from \"./ir.js\"\nimport type { LazyCollectionCallbacks } from \"./compiler/joins.js\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every((collection) =>\n collection.isReady()\n )\n }\n\n const allCollectionsReadyOrInitialCommit = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n let collectionWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n\n // Map of collection IDs to functions that load keys for that lazy collection\n const lazyCollectionsCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of collection IDs that are lazy collections\n const lazyCollections = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n const optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> =\n {}\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n\n // Compile the query and get both pipeline and collection WHERE clauses\n ;({\n pipeline: pipelineCache,\n collectionWhereClauses: collectionWhereClausesCache,\n } = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>,\n collections,\n lazyCollectionsCallbacks,\n lazyCollections,\n optimizableOrderByCollections\n ))\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, markReady, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n let subscribedToAllCollections = false\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happend because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n const maybeRunGraph = (callback?: () => boolean) => {\n // We only run the graph if all the collections are ready\n if (\n allCollectionsReadyOrInitialCommit() &&\n subscribedToAllCollections\n ) {\n graph.run()\n const ready = callback?.() ?? true\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n // Mark the collection as ready after the first successful run\n if (ready && allCollectionsReady()) {\n markReady()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Subscribe to all collections, using WHERE clause optimization when available\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n const collectionAlias = findCollectionAlias(collectionId, query)\n const whereClause =\n collectionAlias && collectionWhereClausesCache\n ? collectionWhereClausesCache.get(collectionAlias)\n : undefined\n\n const sendChangesToPipeline = (\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph(callback)\n }\n\n // Wraps the sendChangesToPipeline function\n // in order to turn `update`s into `insert`s\n // for keys that have not been sent to the pipeline yet\n // and filter out deletes for keys that have not been sent\n const sendVisibleChangesToPipeline = (\n changes: Array<ChangeMessage<any, string | number>>,\n loadedInitialState: boolean,\n sentKeys: Set<string | number>\n ) => {\n if (loadedInitialState) {\n // There was no index for the join key\n // so we loaded the initial state\n // so we can safely assume that the pipeline has seen all keys\n return sendChangesToPipeline(changes)\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert` }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n }\n newChanges.push(newChange)\n }\n\n return sendChangesToPipeline(newChanges)\n }\n\n const loadKeys = (\n keys: Iterable<string | number>,\n sentKeys: Set<string | number>,\n filterFn: (item: object) => boolean\n ) => {\n for (const key of keys) {\n // Only load the key once\n if (sentKeys.has(key)) continue\n\n const value = collection.get(key)\n if (value !== undefined && filterFn(value)) {\n sentKeys.add(key)\n sendChangesToPipeline([{ type: `insert`, key, value }])\n }\n }\n }\n\n const subscribeToAllChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const unsubscribe = collection.subscribeChanges(\n sendChangesToPipeline,\n {\n includeInitialState: true,\n ...(whereExpression ? { whereExpression } : undefined),\n }\n )\n return unsubscribe\n }\n\n // Subscribes to all changes but without the initial state\n // such that we can load keys from the initial state on demand\n // based on the matching keys from the main collection in the join\n const subscribeToMatchingChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n let loadedInitialState = false\n const sentKeys = new Set<string | number>()\n\n const sendVisibleChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n sendVisibleChangesToPipeline(changes, loadedInitialState, sentKeys)\n }\n\n const unsubscribe = collection.subscribeChanges(sendVisibleChanges, {\n whereExpression,\n })\n\n // Create a function that loads keys from the collection\n // into the query pipeline on demand\n const filterFn = whereExpression\n ? createFilterFunctionFromExpression(whereExpression)\n : () => true\n const loadKs = (keys: Set<string | number>) => {\n return loadKeys(keys, sentKeys, filterFn)\n }\n\n // Store the functions to load keys and load initial state in the `lazyCollectionsCallbacks` map\n // This is used by the join operator to dynamically load matching keys from the lazy collection\n // or to get the full initial state of the collection if there's no index for the join key\n lazyCollectionsCallbacks[collectionId] = {\n loadKeys: loadKs,\n loadInitialState: () => {\n // Make sure we only load the initial state once\n if (loadedInitialState) return\n loadedInitialState = true\n\n const changes = collection.currentStateAsChanges({\n whereExpression,\n })\n sendChangesToPipeline(changes)\n },\n }\n return unsubscribe\n }\n\n const subscribeToOrderedChanges = (\n whereExpression: BasicExpression<boolean> | undefined\n ) => {\n const {\n offset,\n limit,\n comparator,\n index,\n dataNeeded,\n valueExtractorForRawRow,\n } = optimizableOrderByCollections[collectionId]!\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${collectionId}`\n )\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n const loadMoreIfNeeded = () => {\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n loadNextItems(n)\n }\n\n // Indicate that we're done loading data if we didn't need to load more data\n return n === 0\n }\n\n // Keep track of the keys we've sent\n // and also the biggest value we've sent so far\n const sentValuesInfo: {\n sentKeys: Set<string | number>\n biggest: any\n } = {\n sentKeys: new Set<string | number>(),\n biggest: undefined,\n }\n\n const sendChangesToPipelineWithTracking = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n const trackedChanges = trackSentValues(\n changes,\n comparator,\n sentValuesInfo\n )\n sendChangesToPipeline(trackedChanges, loadMoreIfNeeded)\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n const loadNextItems = (n: number) => {\n const biggestSentRow = sentValuesInfo.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n // Take the `n` items after the biggest sent value\n const nextOrderedKeys = index.take(n, biggestSentValue)\n const nextInserts: Array<ChangeMessage<any, string | number>> =\n nextOrderedKeys.map((key) => {\n return { type: `insert`, key, value: collection.get(key) }\n })\n sendChangesToPipelineWithTracking(nextInserts)\n }\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n loadNextItems(offset + limit)\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK\n const splittedChanges = splitUpdates(changes)\n const filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n sentValuesInfo.biggest\n )\n sendChangesToPipeline(filteredChanges, loadMoreIfNeeded)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const unsubscribe = collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n return unsubscribe\n }\n\n const subscribeToChanges = (\n whereExpression?: BasicExpression<boolean>\n ) => {\n let unsubscribe: () => void\n if (lazyCollections.has(collectionId)) {\n unsubscribe = subscribeToMatchingChanges(whereExpression)\n } else if (\n Object.hasOwn(optimizableOrderByCollections, collectionId)\n ) {\n unsubscribe = subscribeToOrderedChanges(whereExpression)\n } else {\n unsubscribe = subscribeToAllChanges(whereExpression)\n }\n unsubscribeCallbacks.add(unsubscribe)\n }\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n collectionAlias!\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n subscribeToChanges()\n }\n })\n\n subscribedToAllCollections = true\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n\n // Reset caches so a fresh graph/pipeline is compiled on next start\n // This avoids reusing a finalized D2 graph across GC restarts\n graphCache = undefined\n inputsCache = undefined\n pipelineCache = undefined\n collectionWhereClausesCache = undefined\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Converts WHERE expressions from the query IR into a BasicExpression for subscribeChanges\n *\n * @param whereExpressions Array of WHERE expressions to convert\n * @param tableAlias The table alias used in the expressions\n * @returns A BasicExpression that can be used with the collection's index system\n */\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\nfunction* trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n tracker: { sentKeys: Set<string | number>; biggest: any }\n) {\n for (const change of changes) {\n tracker.sentKeys.add(change.key)\n\n if (!tracker.biggest) {\n tracker.biggest = change.value\n } else if (comparator(tracker.biggest, change.value) < 0) {\n tracker.biggest = change.value\n }\n\n yield change\n }\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller than the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -625,6 +625,13 @@ export function liveQueryCollectionOptions<
|
|
|
625
625
|
// Return the unsubscribe function
|
|
626
626
|
return () => {
|
|
627
627
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())
|
|
628
|
+
|
|
629
|
+
// Reset caches so a fresh graph/pipeline is compiled on next start
|
|
630
|
+
// This avoids reusing a finalized D2 graph across GC restarts
|
|
631
|
+
graphCache = undefined
|
|
632
|
+
inputsCache = undefined
|
|
633
|
+
pipelineCache = undefined
|
|
634
|
+
collectionWhereClausesCache = undefined
|
|
628
635
|
}
|
|
629
636
|
},
|
|
630
637
|
}
|