@tanstack/db 0.4.8 → 0.4.9
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/errors.cjs +51 -17
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +38 -8
- package/dist/cjs/index.cjs +8 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +1 -1
- package/dist/cjs/query/compiler/index.cjs +42 -19
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +33 -8
- package/dist/cjs/query/compiler/joins.cjs +88 -66
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +5 -2
- package/dist/cjs/query/compiler/order-by.cjs +2 -0
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +276 -42
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +84 -8
- package/dist/cjs/query/live/collection-registry.cjs +16 -0
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -0
- package/dist/cjs/query/live/collection-registry.d.cts +26 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +57 -58
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +4 -7
- package/dist/cjs/query/live-query-collection.cjs +11 -5
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +10 -3
- package/dist/cjs/query/optimizer.cjs +44 -7
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/optimizer.d.cts +4 -4
- package/dist/cjs/scheduler.cjs +137 -0
- package/dist/cjs/scheduler.cjs.map +1 -0
- package/dist/cjs/scheduler.d.cts +56 -0
- package/dist/cjs/transactions.cjs +7 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/esm/errors.d.ts +38 -8
- package/dist/esm/errors.js +52 -18
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +9 -5
- package/dist/esm/query/builder/types.d.ts +1 -1
- package/dist/esm/query/compiler/index.d.ts +33 -8
- package/dist/esm/query/compiler/index.js +42 -19
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +5 -2
- package/dist/esm/query/compiler/joins.js +90 -68
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -0
- package/dist/esm/query/compiler/order-by.js +2 -0
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +84 -8
- package/dist/esm/query/live/collection-config-builder.js +276 -42
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.d.ts +26 -0
- package/dist/esm/query/live/collection-registry.js +16 -0
- package/dist/esm/query/live/collection-registry.js.map +1 -0
- package/dist/esm/query/live/collection-subscriber.d.ts +4 -7
- package/dist/esm/query/live/collection-subscriber.js +57 -58
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +10 -3
- package/dist/esm/query/live-query-collection.js +11 -5
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.d.ts +4 -4
- package/dist/esm/query/optimizer.js +44 -7
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/scheduler.d.ts +56 -0
- package/dist/esm/scheduler.js +137 -0
- package/dist/esm/scheduler.js.map +1 -0
- package/dist/esm/transactions.js +7 -1
- package/dist/esm/transactions.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.ts +79 -13
- package/src/query/builder/types.ts +1 -1
- package/src/query/compiler/index.ts +115 -32
- package/src/query/compiler/joins.ts +180 -127
- package/src/query/compiler/order-by.ts +7 -0
- package/src/query/compiler/select.ts +2 -3
- package/src/query/live/collection-config-builder.ts +450 -58
- package/src/query/live/collection-registry.ts +47 -0
- package/src/query/live/collection-subscriber.ts +88 -106
- package/src/query/live-query-collection.ts +39 -14
- package/src/query/optimizer.ts +85 -15
- package/src/scheduler.ts +198 -0
- package/src/transactions.ts +12 -1
|
@@ -5,12 +5,15 @@ import { Collection } from '../../collection/index.js';
|
|
|
5
5
|
import { KeyedStream, NamespacedAndKeyedStream } from '../../types.js';
|
|
6
6
|
import { QueryCache, QueryMapping } from './types.js';
|
|
7
7
|
import { CollectionSubscription } from '../../collection/subscription.js';
|
|
8
|
+
/** Function type for loading specific keys into a lazy collection */
|
|
8
9
|
export type LoadKeysFn = (key: Set<string | number>) => void;
|
|
10
|
+
/** Callbacks for managing lazy-loaded collections in optimized joins */
|
|
9
11
|
export type LazyCollectionCallbacks = {
|
|
10
12
|
loadKeys: LoadKeysFn;
|
|
11
13
|
loadInitialState: () => void;
|
|
12
14
|
};
|
|
13
15
|
/**
|
|
14
|
-
* Processes all join clauses
|
|
16
|
+
* Processes all join clauses, applying lazy loading optimizations and maintaining
|
|
17
|
+
* alias tracking for per-alias subscriptions (enables self-joins).
|
|
15
18
|
*/
|
|
16
|
-
export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>,
|
|
19
|
+
export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, sources: Record<string, KeyedStream>, mainCollectionId: string, mainSource: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping, collections: Record<string, Collection>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazySources: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, rawQuery: QueryIR, onCompileSubquery: CompileQueryFn, aliasToCollectionId: Record<string, string>, aliasRemapping: Record<string, string>): NamespacedAndKeyedStream;
|
|
@@ -88,7 +88,9 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
|
|
|
88
88
|
clause.compareOptions
|
|
89
89
|
);
|
|
90
90
|
if (index && index.supports(`gt`)) {
|
|
91
|
+
const orderByAlias = orderByExpression.path.length > 1 ? String(orderByExpression.path[0]) : rawQuery.from.alias;
|
|
91
92
|
const orderByOptimizationInfo = {
|
|
93
|
+
alias: orderByAlias,
|
|
92
94
|
offset: offset ?? 0,
|
|
93
95
|
limit,
|
|
94
96
|
comparator,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"order-by.cjs","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { defaultComparator, makeComparator } from \"../../utils/comparison.js\"\nimport { PropRef, followRef } from \"../ir.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { replaceAggregatesByRefs } from \"./group-by.js\"\nimport type { CompiledSingleRowExpression } from \"./evaluators.js\"\nimport type { OrderBy, OrderByClause, QueryIR, Select } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\nimport type { IndexInterface } from \"../../indexes/base-index.js\"\nimport type { Collection } from \"../../collection/index.js\"\n\nexport type OrderByOptimizationInfo = {\n orderBy: OrderBy\n offset: number\n limit: number\n comparator: (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => number\n valueExtractorForRawRow: (row: Record<string, unknown>) => any\n index: IndexInterface<string | number>\n dataNeeded?: () => number\n}\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n rawQuery: QueryIR,\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n selectClause: Select,\n collection: Collection,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => {\n const clauseWithoutAggregates = replaceAggregatesByRefs(\n clause.expression,\n selectClause,\n `__select_results`\n )\n return {\n compiledExpression: compileExpression(clauseWithoutAggregates),\n compareOptions: clause.compareOptions,\n }\n })\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // The namespaced row contains:\n // 1. Table aliases as top-level properties (e.g., row[\"tableName\"])\n // 2. SELECT results in __select_results (e.g., row.__select_results[\"aggregateAlias\"])\n // The replaceAggregatesByRefs function has already transformed any aggregate expressions\n // that match SELECT aggregates to use the __select_results namespace.\n const orderByContext = row\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const compare = (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const clause = orderByClause[i]!\n const compareFn = makeComparator(clause.compareOptions)\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const compareFn = makeComparator(clause.compareOptions)\n return compareFn(a, b)\n }\n\n return defaultComparator(a, b)\n }\n\n let setSizeCallback: ((getSize: () => number) => void) | undefined\n\n // Optimize the orderBy operator to lazily load elements\n // by using the range index of the collection.\n // Only for orderBy clause on a single column for now (no composite ordering)\n if (limit && orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const orderByExpression = clause.expression\n\n if (orderByExpression.type === `ref`) {\n const followRefResult = followRef(\n rawQuery,\n orderByExpression,\n collection\n )!\n\n const followRefCollection = followRefResult.collection\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n clause.compareOptions,\n compare\n )\n }\n\n const valueExtractorForRawRow = compileExpression(\n new PropRef(followRefResult.path),\n true\n ) as CompiledSingleRowExpression\n\n const comparator = (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => {\n const extractedA = a ? valueExtractorForRawRow(a) : a\n const extractedB = b ? valueExtractorForRawRow(b) : b\n return compare(extractedA, extractedB)\n }\n\n const index: IndexInterface<string | number> | undefined =\n findIndexForField(\n followRefCollection.indexes,\n followRefResult.path,\n clause.compareOptions\n )\n\n if (index && index.supports(`gt`)) {\n // We found an index that we can use to lazily load ordered data\n const orderByOptimizationInfo = {\n offset: offset ?? 0,\n limit,\n comparator,\n valueExtractorForRawRow,\n index,\n orderBy: orderByClause,\n }\n\n optimizableOrderByCollections[followRefCollection.id] =\n orderByOptimizationInfo\n\n setSizeCallback = (getSize: () => number) => {\n optimizableOrderByCollections[followRefCollection.id] = {\n ...optimizableOrderByCollections[followRefCollection.id]!,\n dataNeeded: () => {\n const size = getSize()\n return Math.max(0, limit - size)\n },\n }\n }\n }\n }\n }\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator: compare,\n setSizeCallback,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":["replaceAggregatesByRefs","compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","PropRef","findIndexForField","orderByWithFractionalIndex"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"order-by.cjs","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { defaultComparator, makeComparator } from \"../../utils/comparison.js\"\nimport { PropRef, followRef } from \"../ir.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { replaceAggregatesByRefs } from \"./group-by.js\"\nimport type { CompiledSingleRowExpression } from \"./evaluators.js\"\nimport type { OrderBy, OrderByClause, QueryIR, Select } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\nimport type { IndexInterface } from \"../../indexes/base-index.js\"\nimport type { Collection } from \"../../collection/index.js\"\n\nexport type OrderByOptimizationInfo = {\n alias: string\n orderBy: OrderBy\n offset: number\n limit: number\n comparator: (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => number\n valueExtractorForRawRow: (row: Record<string, unknown>) => any\n index: IndexInterface<string | number>\n dataNeeded?: () => number\n}\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n rawQuery: QueryIR,\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n selectClause: Select,\n collection: Collection,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => {\n const clauseWithoutAggregates = replaceAggregatesByRefs(\n clause.expression,\n selectClause,\n `__select_results`\n )\n return {\n compiledExpression: compileExpression(clauseWithoutAggregates),\n compareOptions: clause.compareOptions,\n }\n })\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // The namespaced row contains:\n // 1. Table aliases as top-level properties (e.g., row[\"tableName\"])\n // 2. SELECT results in __select_results (e.g., row.__select_results[\"aggregateAlias\"])\n // The replaceAggregatesByRefs function has already transformed any aggregate expressions\n // that match SELECT aggregates to use the __select_results namespace.\n const orderByContext = row\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const compare = (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const clause = orderByClause[i]!\n const compareFn = makeComparator(clause.compareOptions)\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const compareFn = makeComparator(clause.compareOptions)\n return compareFn(a, b)\n }\n\n return defaultComparator(a, b)\n }\n\n let setSizeCallback: ((getSize: () => number) => void) | undefined\n\n // Optimize the orderBy operator to lazily load elements\n // by using the range index of the collection.\n // Only for orderBy clause on a single column for now (no composite ordering)\n if (limit && orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const orderByExpression = clause.expression\n\n if (orderByExpression.type === `ref`) {\n const followRefResult = followRef(\n rawQuery,\n orderByExpression,\n collection\n )!\n\n const followRefCollection = followRefResult.collection\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n clause.compareOptions,\n compare\n )\n }\n\n const valueExtractorForRawRow = compileExpression(\n new PropRef(followRefResult.path),\n true\n ) as CompiledSingleRowExpression\n\n const comparator = (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => {\n const extractedA = a ? valueExtractorForRawRow(a) : a\n const extractedB = b ? valueExtractorForRawRow(b) : b\n return compare(extractedA, extractedB)\n }\n\n const index: IndexInterface<string | number> | undefined =\n findIndexForField(\n followRefCollection.indexes,\n followRefResult.path,\n clause.compareOptions\n )\n\n if (index && index.supports(`gt`)) {\n // We found an index that we can use to lazily load ordered data\n const orderByAlias =\n orderByExpression.path.length > 1\n ? String(orderByExpression.path[0])\n : rawQuery.from.alias\n\n const orderByOptimizationInfo = {\n alias: orderByAlias,\n offset: offset ?? 0,\n limit,\n comparator,\n valueExtractorForRawRow,\n index,\n orderBy: orderByClause,\n }\n\n optimizableOrderByCollections[followRefCollection.id] =\n orderByOptimizationInfo\n\n setSizeCallback = (getSize: () => number) => {\n optimizableOrderByCollections[followRefCollection.id] = {\n ...optimizableOrderByCollections[followRefCollection.id]!,\n dataNeeded: () => {\n const size = getSize()\n return Math.max(0, limit - size)\n },\n }\n }\n }\n }\n }\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator: compare,\n setSizeCallback,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":["replaceAggregatesByRefs","compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","PropRef","findIndexForField","orderByWithFractionalIndex"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { IStreamBuilder, KeyValue } from '@tanstack/db-ivm';
|
|
|
4
4
|
import { IndexInterface } from '../../indexes/base-index.js';
|
|
5
5
|
import { Collection } from '../../collection/index.js';
|
|
6
6
|
export type OrderByOptimizationInfo = {
|
|
7
|
+
alias: string;
|
|
7
8
|
orderBy: OrderBy;
|
|
8
9
|
offset: number;
|
|
9
10
|
limit: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.cjs","sources":["../../../../src/query/compiler/select.ts"],"sourcesContent":["import { map } from \"@tanstack/db-ivm\"\nimport { PropRef, Value as ValClass, isExpressionLike } from \"../ir.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { Aggregate, BasicExpression, Select } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\n\n/**\n * Type for operations array used in select processing\n */\ntype SelectOp =\n | {\n kind: `merge`\n targetPath: Array<string>\n source: (row: NamespacedRow) => any\n }\n | { kind: `field`; alias: string; compiled: (row: NamespacedRow) => any }\n\n/**\n * Unwraps any Value expressions\n */\nfunction unwrapVal(input: any): any {\n if (input instanceof ValClass) return input.value\n return input\n}\n\n/**\n * Processes a merge operation by merging source values into the target path\n */\nfunction processMerge(\n op: Extract<SelectOp, { kind: `merge` }>,\n namespacedRow: NamespacedRow,\n selectResults: Record<string, any>\n): void {\n const value = op.source(namespacedRow)\n if (value && typeof value === `object`) {\n // Ensure target object exists\n let cursor: any = selectResults\n const path = op.targetPath\n if (path.length === 0) {\n // Top-level merge\n for (const [k, v] of Object.entries(value)) {\n selectResults[k] = unwrapVal(v)\n }\n } else {\n for (let i = 0; i < path.length; i++) {\n const seg = path[i]!\n if (i === path.length - 1) {\n const dest = (cursor[seg] ??= {})\n if (typeof dest === `object`) {\n for (const [k, v] of Object.entries(value)) {\n dest[k] = unwrapVal(v)\n }\n }\n } else {\n const next = cursor[seg]\n if (next == null || typeof next !== `object`) {\n cursor[seg] = {}\n }\n cursor = cursor[seg]\n }\n }\n }\n }\n}\n\n/**\n * Processes a non-merge operation by setting the field value at the specified alias path\n */\nfunction processNonMergeOp(\n op: Extract<SelectOp, { kind: `field` }>,\n namespacedRow: NamespacedRow,\n selectResults: Record<string, any>\n): void {\n // Support nested alias paths like \"meta.author.name\"\n const path = op.alias.split(`.`)\n if (path.length === 1) {\n selectResults[op.alias] = op.compiled(namespacedRow)\n } else {\n let cursor: any = selectResults\n for (let i = 0; i < path.length - 1; i++) {\n const seg = path[i]!\n const next = cursor[seg]\n if (next == null || typeof next !== `object`) {\n cursor[seg] = {}\n }\n cursor = cursor[seg]\n }\n cursor[path[path.length - 1]!] = unwrapVal(op.compiled(namespacedRow))\n }\n}\n\n/**\n * Processes a single row to generate select results\n */\nfunction processRow(\n [key, namespacedRow]: [unknown, NamespacedRow],\n ops: Array<SelectOp>\n): [unknown, typeof namespacedRow & { __select_results: any }] {\n const selectResults: Record<string, any> = {}\n\n for (const op of ops) {\n if (op.kind === `merge`) {\n processMerge(op, namespacedRow, selectResults)\n } else {\n processNonMergeOp(op, namespacedRow, selectResults)\n }\n }\n\n // Return the namespaced row with __select_results added\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [\n unknown,\n typeof namespacedRow & { __select_results: typeof selectResults },\n ]\n}\n\n/**\n * Processes the SELECT clause and places results in __select_results\n * while preserving the original namespaced row for ORDER BY access\n */\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n select: Select,\n _allInputs: Record<string, KeyedStream>\n): NamespacedAndKeyedStream {\n // Build ordered operations to preserve authoring order (spreads and fields)\n const ops: Array<SelectOp> = []\n\n addFromObject([], select, ops)\n\n return pipeline.pipe(map((row) => processRow(row, ops)))\n}\n\n/**\n * Helper function to check if an expression is an aggregate\n */\nfunction isAggregateExpression(\n expr: BasicExpression | Aggregate\n): expr is Aggregate {\n return expr.type === `agg`\n}\n\n/**\n * Processes a single argument in a function context\n */\nexport function processArgument(\n arg: BasicExpression | Aggregate,\n namespacedRow: NamespacedRow\n): any {\n if (isAggregateExpression(arg)) {\n throw new Error(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`\n )\n }\n\n // Pre-compile the expression and evaluate immediately\n const compiledExpression = compileExpression(arg)\n const value = compiledExpression(namespacedRow)\n\n return value\n}\n\n/**\n * Helper function to check if an object is a nested select object\n *\n * .select({\n * id: users.id,\n * profile: { // <-- this is a nested select object\n * name: users.name,\n * email: users.email\n * }\n * })\n */\nfunction isNestedSelectObject(obj: any): boolean {\n return obj && typeof obj === `object` && !isExpressionLike(obj)\n}\n\n/**\n * Helper function to process select objects and build operations array\n */\nfunction addFromObject(\n prefixPath: Array<string>,\n obj: any,\n ops: Array<SelectOp>\n) {\n for (const [key, value] of Object.entries(obj)) {\n if (key.startsWith(`__SPREAD_SENTINEL__`)) {\n const rest = key.slice(`__SPREAD_SENTINEL__`.length)\n const splitIndex = rest.lastIndexOf(`__`)\n const pathStr = splitIndex >= 0 ? rest.slice(0, splitIndex) : rest\n const isRefExpr =\n value &&\n typeof value === `object` &&\n `type` in (value as any) &&\n (value as any).type === `ref`\n if (pathStr.includes(`.`) || isRefExpr) {\n // Merge into the current destination (prefixPath) from the referenced source path\n const targetPath = [...prefixPath]\n const expr = isRefExpr\n ? (value as BasicExpression)\n : (new PropRef(pathStr.split(`.`)) as BasicExpression)\n const compiled = compileExpression(expr)\n ops.push({ kind: `merge`, targetPath, source: compiled })\n } else {\n // Table-level: pathStr is the alias; merge from namespaced row at the current prefix\n const tableAlias = pathStr\n const targetPath = [...prefixPath]\n ops.push({\n kind: `merge`,\n targetPath,\n source: (row) => (row as any)[tableAlias],\n })\n }\n continue\n }\n\n const expression = value as any\n if (isNestedSelectObject(expression)) {\n // Nested selection object\n addFromObject([...prefixPath, key], expression, ops)\n continue\n }\n\n if (isAggregateExpression(expression)) {\n // Placeholder for group-by processing later\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => null,\n })\n } else {\n if (expression === undefined || !isExpressionLike(expression)) {\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => expression,\n })\n continue\n }\n // If the expression is a Value wrapper, embed the literal to avoid re-compilation mishaps\n if (expression instanceof ValClass) {\n const val = expression.value\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => val,\n })\n } else {\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: compileExpression(expression as BasicExpression),\n })\n }\n }\n }\n}\n"],"names":["ValClass","map","isExpressionLike","PropRef","compileExpression"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.cjs","sources":["../../../../src/query/compiler/select.ts"],"sourcesContent":["import { map } from \"@tanstack/db-ivm\"\nimport { PropRef, Value as ValClass, isExpressionLike } from \"../ir.js\"\nimport { AggregateNotSupportedError } from \"../../errors.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { Aggregate, BasicExpression, Select } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\n\n/**\n * Type for operations array used in select processing\n */\ntype SelectOp =\n | {\n kind: `merge`\n targetPath: Array<string>\n source: (row: NamespacedRow) => any\n }\n | { kind: `field`; alias: string; compiled: (row: NamespacedRow) => any }\n\n/**\n * Unwraps any Value expressions\n */\nfunction unwrapVal(input: any): any {\n if (input instanceof ValClass) return input.value\n return input\n}\n\n/**\n * Processes a merge operation by merging source values into the target path\n */\nfunction processMerge(\n op: Extract<SelectOp, { kind: `merge` }>,\n namespacedRow: NamespacedRow,\n selectResults: Record<string, any>\n): void {\n const value = op.source(namespacedRow)\n if (value && typeof value === `object`) {\n // Ensure target object exists\n let cursor: any = selectResults\n const path = op.targetPath\n if (path.length === 0) {\n // Top-level merge\n for (const [k, v] of Object.entries(value)) {\n selectResults[k] = unwrapVal(v)\n }\n } else {\n for (let i = 0; i < path.length; i++) {\n const seg = path[i]!\n if (i === path.length - 1) {\n const dest = (cursor[seg] ??= {})\n if (typeof dest === `object`) {\n for (const [k, v] of Object.entries(value)) {\n dest[k] = unwrapVal(v)\n }\n }\n } else {\n const next = cursor[seg]\n if (next == null || typeof next !== `object`) {\n cursor[seg] = {}\n }\n cursor = cursor[seg]\n }\n }\n }\n }\n}\n\n/**\n * Processes a non-merge operation by setting the field value at the specified alias path\n */\nfunction processNonMergeOp(\n op: Extract<SelectOp, { kind: `field` }>,\n namespacedRow: NamespacedRow,\n selectResults: Record<string, any>\n): void {\n // Support nested alias paths like \"meta.author.name\"\n const path = op.alias.split(`.`)\n if (path.length === 1) {\n selectResults[op.alias] = op.compiled(namespacedRow)\n } else {\n let cursor: any = selectResults\n for (let i = 0; i < path.length - 1; i++) {\n const seg = path[i]!\n const next = cursor[seg]\n if (next == null || typeof next !== `object`) {\n cursor[seg] = {}\n }\n cursor = cursor[seg]\n }\n cursor[path[path.length - 1]!] = unwrapVal(op.compiled(namespacedRow))\n }\n}\n\n/**\n * Processes a single row to generate select results\n */\nfunction processRow(\n [key, namespacedRow]: [unknown, NamespacedRow],\n ops: Array<SelectOp>\n): [unknown, typeof namespacedRow & { __select_results: any }] {\n const selectResults: Record<string, any> = {}\n\n for (const op of ops) {\n if (op.kind === `merge`) {\n processMerge(op, namespacedRow, selectResults)\n } else {\n processNonMergeOp(op, namespacedRow, selectResults)\n }\n }\n\n // Return the namespaced row with __select_results added\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [\n unknown,\n typeof namespacedRow & { __select_results: typeof selectResults },\n ]\n}\n\n/**\n * Processes the SELECT clause and places results in __select_results\n * while preserving the original namespaced row for ORDER BY access\n */\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n select: Select,\n _allInputs: Record<string, KeyedStream>\n): NamespacedAndKeyedStream {\n // Build ordered operations to preserve authoring order (spreads and fields)\n const ops: Array<SelectOp> = []\n\n addFromObject([], select, ops)\n\n return pipeline.pipe(map((row) => processRow(row, ops)))\n}\n\n/**\n * Helper function to check if an expression is an aggregate\n */\nfunction isAggregateExpression(\n expr: BasicExpression | Aggregate\n): expr is Aggregate {\n return expr.type === `agg`\n}\n\n/**\n * Processes a single argument in a function context\n */\nexport function processArgument(\n arg: BasicExpression | Aggregate,\n namespacedRow: NamespacedRow\n): any {\n if (isAggregateExpression(arg)) {\n throw new AggregateNotSupportedError()\n }\n\n // Pre-compile the expression and evaluate immediately\n const compiledExpression = compileExpression(arg)\n const value = compiledExpression(namespacedRow)\n\n return value\n}\n\n/**\n * Helper function to check if an object is a nested select object\n *\n * .select({\n * id: users.id,\n * profile: { // <-- this is a nested select object\n * name: users.name,\n * email: users.email\n * }\n * })\n */\nfunction isNestedSelectObject(obj: any): boolean {\n return obj && typeof obj === `object` && !isExpressionLike(obj)\n}\n\n/**\n * Helper function to process select objects and build operations array\n */\nfunction addFromObject(\n prefixPath: Array<string>,\n obj: any,\n ops: Array<SelectOp>\n) {\n for (const [key, value] of Object.entries(obj)) {\n if (key.startsWith(`__SPREAD_SENTINEL__`)) {\n const rest = key.slice(`__SPREAD_SENTINEL__`.length)\n const splitIndex = rest.lastIndexOf(`__`)\n const pathStr = splitIndex >= 0 ? rest.slice(0, splitIndex) : rest\n const isRefExpr =\n value &&\n typeof value === `object` &&\n `type` in (value as any) &&\n (value as any).type === `ref`\n if (pathStr.includes(`.`) || isRefExpr) {\n // Merge into the current destination (prefixPath) from the referenced source path\n const targetPath = [...prefixPath]\n const expr = isRefExpr\n ? (value as BasicExpression)\n : (new PropRef(pathStr.split(`.`)) as BasicExpression)\n const compiled = compileExpression(expr)\n ops.push({ kind: `merge`, targetPath, source: compiled })\n } else {\n // Table-level: pathStr is the alias; merge from namespaced row at the current prefix\n const tableAlias = pathStr\n const targetPath = [...prefixPath]\n ops.push({\n kind: `merge`,\n targetPath,\n source: (row) => (row as any)[tableAlias],\n })\n }\n continue\n }\n\n const expression = value as any\n if (isNestedSelectObject(expression)) {\n // Nested selection object\n addFromObject([...prefixPath, key], expression, ops)\n continue\n }\n\n if (isAggregateExpression(expression)) {\n // Placeholder for group-by processing later\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => null,\n })\n } else {\n if (expression === undefined || !isExpressionLike(expression)) {\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => expression,\n })\n continue\n }\n // If the expression is a Value wrapper, embed the literal to avoid re-compilation mishaps\n if (expression instanceof ValClass) {\n const val = expression.value\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: () => val,\n })\n } else {\n ops.push({\n kind: `field`,\n alias: [...prefixPath, key].join(`.`),\n compiled: compileExpression(expression as BasicExpression),\n })\n }\n }\n }\n}\n"],"names":["ValClass","map","isExpressionLike","PropRef","compileExpression"],"mappings":"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"}
|
|
@@ -3,22 +3,40 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const dbIvm = require("@tanstack/db-ivm");
|
|
4
4
|
const index = require("../compiler/index.cjs");
|
|
5
5
|
const index$1 = require("../builder/index.cjs");
|
|
6
|
+
const errors = require("../../errors.cjs");
|
|
7
|
+
const scheduler = require("../../scheduler.cjs");
|
|
8
|
+
const transactions = require("../../transactions.cjs");
|
|
6
9
|
const collectionSubscriber = require("./collection-subscriber.cjs");
|
|
10
|
+
const collectionRegistry = require("./collection-registry.cjs");
|
|
7
11
|
let liveQueryCollectionCounter = 0;
|
|
8
12
|
class CollectionConfigBuilder {
|
|
9
13
|
constructor(config) {
|
|
10
14
|
this.config = config;
|
|
15
|
+
this.compiledAliasToCollectionId = {};
|
|
11
16
|
this.resultKeys = /* @__PURE__ */ new WeakMap();
|
|
12
17
|
this.orderByIndices = /* @__PURE__ */ new WeakMap();
|
|
13
18
|
this.isGraphRunning = false;
|
|
19
|
+
this.runCount = 0;
|
|
14
20
|
this.isInErrorState = false;
|
|
21
|
+
this.aliasDependencies = {};
|
|
22
|
+
this.builderDependencies = /* @__PURE__ */ new Set();
|
|
23
|
+
this.pendingGraphRuns = /* @__PURE__ */ new Map();
|
|
15
24
|
this.subscriptions = {};
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
25
|
+
this.lazySourcesCallbacks = {};
|
|
26
|
+
this.lazySources = /* @__PURE__ */ new Set();
|
|
18
27
|
this.optimizableOrderByCollections = {};
|
|
19
28
|
this.id = config.id || `live-query-${++liveQueryCollectionCounter}`;
|
|
20
29
|
this.query = buildQueryFromConfig(config);
|
|
21
30
|
this.collections = extractCollectionsFromQuery(this.query);
|
|
31
|
+
const collectionAliasesById = extractCollectionAliases(this.query);
|
|
32
|
+
this.collectionByAlias = {};
|
|
33
|
+
for (const [collectionId, aliases] of collectionAliasesById.entries()) {
|
|
34
|
+
const collection = this.collections[collectionId];
|
|
35
|
+
if (!collection) continue;
|
|
36
|
+
for (const alias of aliases) {
|
|
37
|
+
this.collectionByAlias[alias] = collection;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
22
40
|
if (this.query.orderBy && this.query.orderBy.length > 0) {
|
|
23
41
|
this.compare = createOrderByComparator(this.orderByIndices);
|
|
24
42
|
}
|
|
@@ -37,9 +55,38 @@ class CollectionConfigBuilder {
|
|
|
37
55
|
onUpdate: this.config.onUpdate,
|
|
38
56
|
onDelete: this.config.onDelete,
|
|
39
57
|
startSync: this.config.startSync,
|
|
40
|
-
singleResult: this.query.singleResult
|
|
58
|
+
singleResult: this.query.singleResult,
|
|
59
|
+
utils: {
|
|
60
|
+
getRunCount: this.getRunCount.bind(this),
|
|
61
|
+
getBuilder: () => this
|
|
62
|
+
}
|
|
41
63
|
};
|
|
42
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolves a collection alias to its collection ID.
|
|
67
|
+
*
|
|
68
|
+
* Uses a two-tier lookup strategy:
|
|
69
|
+
* 1. First checks compiled aliases (includes subquery inner aliases)
|
|
70
|
+
* 2. Falls back to declared aliases from the query's from/join clauses
|
|
71
|
+
*
|
|
72
|
+
* @param alias - The alias to resolve (e.g., "employee", "manager")
|
|
73
|
+
* @returns The collection ID that the alias references
|
|
74
|
+
* @throws {Error} If the alias is not found in either lookup
|
|
75
|
+
*/
|
|
76
|
+
getCollectionIdForAlias(alias) {
|
|
77
|
+
const compiled = this.compiledAliasToCollectionId[alias];
|
|
78
|
+
if (compiled) {
|
|
79
|
+
return compiled;
|
|
80
|
+
}
|
|
81
|
+
const collection = this.collectionByAlias[alias];
|
|
82
|
+
if (collection) {
|
|
83
|
+
return collection.id;
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`Unknown source alias "${alias}"`);
|
|
86
|
+
}
|
|
87
|
+
isLazyAlias(alias) {
|
|
88
|
+
return this.lazySources.has(alias);
|
|
89
|
+
}
|
|
43
90
|
// The callback function is called after the graph has run.
|
|
44
91
|
// This gives the callback a chance to load more data if needed,
|
|
45
92
|
// that's used to optimize orderBy operators that set a limit,
|
|
@@ -47,14 +94,20 @@ class CollectionConfigBuilder {
|
|
|
47
94
|
// That can happen because even though we load N rows, the pipeline might filter some of these rows out
|
|
48
95
|
// causing the orderBy operator to receive less than N rows or even no rows at all.
|
|
49
96
|
// So this callback would notice that it doesn't have enough rows and load some more.
|
|
50
|
-
// The callback returns a boolean, when it's true it's done loading data.
|
|
51
|
-
maybeRunGraph(
|
|
97
|
+
// The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.
|
|
98
|
+
maybeRunGraph(callback) {
|
|
52
99
|
if (this.isGraphRunning) {
|
|
53
100
|
return;
|
|
54
101
|
}
|
|
102
|
+
if (!this.currentSyncConfig || !this.currentSyncState) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`maybeRunGraph called without active sync session. This should not happen.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
55
107
|
this.isGraphRunning = true;
|
|
56
108
|
try {
|
|
57
|
-
const { begin, commit } =
|
|
109
|
+
const { begin, commit } = this.currentSyncConfig;
|
|
110
|
+
const syncState = this.currentSyncState;
|
|
58
111
|
if (this.isInErrorState) {
|
|
59
112
|
return;
|
|
60
113
|
}
|
|
@@ -66,21 +119,136 @@ class CollectionConfigBuilder {
|
|
|
66
119
|
if (syncState.messagesCount === 0) {
|
|
67
120
|
begin();
|
|
68
121
|
commit();
|
|
69
|
-
this.updateLiveQueryStatus(
|
|
122
|
+
this.updateLiveQueryStatus(this.currentSyncConfig);
|
|
70
123
|
}
|
|
71
124
|
}
|
|
72
125
|
} finally {
|
|
73
126
|
this.isGraphRunning = false;
|
|
74
127
|
}
|
|
75
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Schedules a graph run with the transaction-scoped scheduler.
|
|
131
|
+
* Ensures each builder runs at most once per transaction, with automatic dependency tracking
|
|
132
|
+
* to run parent queries before child queries. Outside a transaction, runs immediately.
|
|
133
|
+
*
|
|
134
|
+
* Multiple calls during a transaction are coalesced into a single execution.
|
|
135
|
+
* Dependencies are auto-discovered from subscribed live queries, or can be overridden.
|
|
136
|
+
* Load callbacks are combined when entries merge.
|
|
137
|
+
*
|
|
138
|
+
* Uses the current sync session's config and syncState from instance properties.
|
|
139
|
+
*
|
|
140
|
+
* @param callback - Optional callback to load more data if needed (returns true when done)
|
|
141
|
+
* @param options - Optional scheduling configuration
|
|
142
|
+
* @param options.contextId - Transaction ID to group work; defaults to active transaction
|
|
143
|
+
* @param options.jobId - Unique identifier for this job; defaults to this builder instance
|
|
144
|
+
* @param options.alias - Source alias that triggered this schedule; adds alias-specific dependencies
|
|
145
|
+
* @param options.dependencies - Explicit dependency list; overrides auto-discovered dependencies
|
|
146
|
+
*/
|
|
147
|
+
scheduleGraphRun(callback, options) {
|
|
148
|
+
const contextId = options?.contextId ?? transactions.getActiveTransaction()?.id;
|
|
149
|
+
const jobId = options?.jobId ?? this;
|
|
150
|
+
const dependentBuilders = (() => {
|
|
151
|
+
if (options?.dependencies) {
|
|
152
|
+
return options.dependencies;
|
|
153
|
+
}
|
|
154
|
+
const deps = new Set(this.builderDependencies);
|
|
155
|
+
if (options?.alias) {
|
|
156
|
+
const aliasDeps = this.aliasDependencies[options.alias];
|
|
157
|
+
if (aliasDeps) {
|
|
158
|
+
for (const dep of aliasDeps) {
|
|
159
|
+
deps.add(dep);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
deps.delete(this);
|
|
164
|
+
return Array.from(deps);
|
|
165
|
+
})();
|
|
166
|
+
if (!this.currentSyncConfig || !this.currentSyncState) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`scheduleGraphRun called without active sync session. This should not happen.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
let pending = contextId ? this.pendingGraphRuns.get(contextId) : void 0;
|
|
172
|
+
if (!pending) {
|
|
173
|
+
pending = {
|
|
174
|
+
loadCallbacks: /* @__PURE__ */ new Set()
|
|
175
|
+
};
|
|
176
|
+
if (contextId) {
|
|
177
|
+
this.pendingGraphRuns.set(contextId, pending);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (callback) {
|
|
181
|
+
pending.loadCallbacks.add(callback);
|
|
182
|
+
}
|
|
183
|
+
const pendingToPass = contextId ? void 0 : pending;
|
|
184
|
+
scheduler.transactionScopedScheduler.schedule({
|
|
185
|
+
contextId,
|
|
186
|
+
jobId,
|
|
187
|
+
dependencies: dependentBuilders,
|
|
188
|
+
run: () => this.executeGraphRun(contextId, pendingToPass)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Clears pending graph run state for a specific context.
|
|
193
|
+
* Called when the scheduler clears a context (e.g., transaction rollback/abort).
|
|
194
|
+
*/
|
|
195
|
+
clearPendingGraphRun(contextId) {
|
|
196
|
+
this.pendingGraphRuns.delete(contextId);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Executes a pending graph run. Called by the scheduler when dependencies are satisfied.
|
|
200
|
+
* Clears the pending state BEFORE execution so that any re-schedules during the run
|
|
201
|
+
* create fresh state and don't interfere with the current execution.
|
|
202
|
+
* Uses instance sync state - if sync has ended, gracefully returns without executing.
|
|
203
|
+
*
|
|
204
|
+
* @param contextId - Optional context ID to look up pending state
|
|
205
|
+
* @param pendingParam - For immediate execution (no context), pending state is passed directly
|
|
206
|
+
*/
|
|
207
|
+
executeGraphRun(contextId, pendingParam) {
|
|
208
|
+
const pending = pendingParam ?? (contextId ? this.pendingGraphRuns.get(contextId) : void 0);
|
|
209
|
+
if (contextId) {
|
|
210
|
+
this.pendingGraphRuns.delete(contextId);
|
|
211
|
+
}
|
|
212
|
+
if (!pending) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!this.currentSyncConfig || !this.currentSyncState) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.incrementRunCount();
|
|
219
|
+
const combinedLoader = () => {
|
|
220
|
+
let allDone = true;
|
|
221
|
+
let firstError;
|
|
222
|
+
pending.loadCallbacks.forEach((loader) => {
|
|
223
|
+
try {
|
|
224
|
+
allDone = loader() && allDone;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
allDone = false;
|
|
227
|
+
firstError ??= error;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (firstError) {
|
|
231
|
+
throw firstError;
|
|
232
|
+
}
|
|
233
|
+
return allDone;
|
|
234
|
+
};
|
|
235
|
+
this.maybeRunGraph(combinedLoader);
|
|
236
|
+
}
|
|
76
237
|
getSyncConfig() {
|
|
77
238
|
return {
|
|
78
239
|
rowUpdateMode: `full`,
|
|
79
240
|
sync: this.syncFn.bind(this)
|
|
80
241
|
};
|
|
81
242
|
}
|
|
243
|
+
incrementRunCount() {
|
|
244
|
+
this.runCount++;
|
|
245
|
+
}
|
|
246
|
+
getRunCount() {
|
|
247
|
+
return this.runCount;
|
|
248
|
+
}
|
|
82
249
|
syncFn(config) {
|
|
83
250
|
this.liveQueryCollection = config.collection;
|
|
251
|
+
this.currentSyncConfig = config;
|
|
84
252
|
const syncState = {
|
|
85
253
|
messagesCount: 0,
|
|
86
254
|
subscribedToAllCollections: false,
|
|
@@ -90,44 +258,66 @@ class CollectionConfigBuilder {
|
|
|
90
258
|
config,
|
|
91
259
|
syncState
|
|
92
260
|
);
|
|
261
|
+
this.currentSyncState = fullSyncState;
|
|
262
|
+
this.unsubscribeFromSchedulerClears = scheduler.transactionScopedScheduler.onClear(
|
|
263
|
+
(contextId) => {
|
|
264
|
+
this.clearPendingGraphRun(contextId);
|
|
265
|
+
}
|
|
266
|
+
);
|
|
93
267
|
const loadMoreDataCallbacks = this.subscribeToAllCollections(
|
|
94
268
|
config,
|
|
95
269
|
fullSyncState
|
|
96
270
|
);
|
|
97
|
-
this.
|
|
271
|
+
this.scheduleGraphRun(loadMoreDataCallbacks);
|
|
98
272
|
return () => {
|
|
99
273
|
syncState.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
274
|
+
this.currentSyncConfig = void 0;
|
|
275
|
+
this.currentSyncState = void 0;
|
|
276
|
+
this.pendingGraphRuns.clear();
|
|
100
277
|
this.graphCache = void 0;
|
|
101
278
|
this.inputsCache = void 0;
|
|
102
279
|
this.pipelineCache = void 0;
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
280
|
+
this.sourceWhereClausesCache = void 0;
|
|
281
|
+
this.lazySources.clear();
|
|
105
282
|
this.optimizableOrderByCollections = {};
|
|
106
|
-
this.
|
|
283
|
+
this.lazySourcesCallbacks = {};
|
|
284
|
+
Object.keys(this.subscriptions).forEach(
|
|
285
|
+
(key) => delete this.subscriptions[key]
|
|
286
|
+
);
|
|
287
|
+
this.compiledAliasToCollectionId = {};
|
|
288
|
+
this.unsubscribeFromSchedulerClears?.();
|
|
289
|
+
this.unsubscribeFromSchedulerClears = void 0;
|
|
107
290
|
};
|
|
108
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Compiles the query pipeline with all declared aliases.
|
|
294
|
+
*/
|
|
109
295
|
compileBasePipeline() {
|
|
110
296
|
this.graphCache = new dbIvm.D2();
|
|
111
297
|
this.inputsCache = Object.fromEntries(
|
|
112
|
-
Object.
|
|
113
|
-
|
|
298
|
+
Object.keys(this.collectionByAlias).map((alias) => [
|
|
299
|
+
alias,
|
|
114
300
|
this.graphCache.newInput()
|
|
115
301
|
])
|
|
116
302
|
);
|
|
117
|
-
const
|
|
118
|
-
pipeline: pipelineCache,
|
|
119
|
-
collectionWhereClauses: collectionWhereClausesCache
|
|
120
|
-
} = index.compileQuery(
|
|
303
|
+
const compilation = index.compileQuery(
|
|
121
304
|
this.query,
|
|
122
305
|
this.inputsCache,
|
|
123
306
|
this.collections,
|
|
124
307
|
this.subscriptions,
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
308
|
+
this.lazySourcesCallbacks,
|
|
309
|
+
this.lazySources,
|
|
127
310
|
this.optimizableOrderByCollections
|
|
128
311
|
);
|
|
129
|
-
this.pipelineCache =
|
|
130
|
-
this.
|
|
312
|
+
this.pipelineCache = compilation.pipeline;
|
|
313
|
+
this.sourceWhereClausesCache = compilation.sourceWhereClauses;
|
|
314
|
+
this.compiledAliasToCollectionId = compilation.aliasToCollectionId;
|
|
315
|
+
const missingAliases = Object.keys(this.compiledAliasToCollectionId).filter(
|
|
316
|
+
(alias) => !Object.hasOwn(this.inputsCache, alias)
|
|
317
|
+
);
|
|
318
|
+
if (missingAliases.length > 0) {
|
|
319
|
+
throw new errors.MissingAliasInputsError(missingAliases);
|
|
320
|
+
}
|
|
131
321
|
}
|
|
132
322
|
maybeCompileBasePipeline() {
|
|
133
323
|
if (!this.graphCache || !this.inputsCache || !this.pipelineCache) {
|
|
@@ -237,29 +427,45 @@ class CollectionConfigBuilder {
|
|
|
237
427
|
(collection) => collection.isReady()
|
|
238
428
|
);
|
|
239
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Creates per-alias subscriptions enabling self-join support.
|
|
432
|
+
* Each alias gets its own subscription with independent filters, even for the same collection.
|
|
433
|
+
* Example: `{ employee: col, manager: col }` creates two separate subscriptions.
|
|
434
|
+
*/
|
|
240
435
|
subscribeToAllCollections(config, syncState) {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
syncState.unsubscribeCallbacks.add(statusUnsubscribe);
|
|
256
|
-
const loadMore = collectionSubscriber$1.loadMoreIfNeeded.bind(
|
|
257
|
-
collectionSubscriber$1,
|
|
258
|
-
subscription
|
|
259
|
-
);
|
|
260
|
-
return loadMore;
|
|
436
|
+
const compiledAliases = Object.entries(this.compiledAliasToCollectionId);
|
|
437
|
+
if (compiledAliases.length === 0) {
|
|
438
|
+
throw new Error(
|
|
439
|
+
`Compiler returned no alias metadata for query '${this.id}'. This should not happen; please report.`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const loaders = compiledAliases.map(([alias, collectionId]) => {
|
|
443
|
+
const collection = this.collectionByAlias[alias] ?? this.collections[collectionId];
|
|
444
|
+
const dependencyBuilder = collectionRegistry.getCollectionBuilder(collection);
|
|
445
|
+
if (dependencyBuilder && dependencyBuilder !== this) {
|
|
446
|
+
this.aliasDependencies[alias] = [dependencyBuilder];
|
|
447
|
+
this.builderDependencies.add(dependencyBuilder);
|
|
448
|
+
} else {
|
|
449
|
+
this.aliasDependencies[alias] = [];
|
|
261
450
|
}
|
|
262
|
-
|
|
451
|
+
const collectionSubscriber$1 = new collectionSubscriber.CollectionSubscriber(
|
|
452
|
+
alias,
|
|
453
|
+
collectionId,
|
|
454
|
+
collection,
|
|
455
|
+
this
|
|
456
|
+
);
|
|
457
|
+
const statusUnsubscribe = collection.on(`status:change`, (event) => {
|
|
458
|
+
this.handleSourceStatusChange(config, collectionId, event);
|
|
459
|
+
});
|
|
460
|
+
syncState.unsubscribeCallbacks.add(statusUnsubscribe);
|
|
461
|
+
const subscription = collectionSubscriber$1.subscribe();
|
|
462
|
+
this.subscriptions[alias] = subscription;
|
|
463
|
+
const loadMore = collectionSubscriber$1.loadMoreIfNeeded.bind(
|
|
464
|
+
collectionSubscriber$1,
|
|
465
|
+
subscription
|
|
466
|
+
);
|
|
467
|
+
return loadMore;
|
|
468
|
+
});
|
|
263
469
|
const loadMoreDataCallback = () => {
|
|
264
470
|
loaders.map((loader) => loader());
|
|
265
471
|
return true;
|
|
@@ -315,6 +521,34 @@ function extractCollectionsFromQuery(query) {
|
|
|
315
521
|
extractFromQuery(query);
|
|
316
522
|
return collections;
|
|
317
523
|
}
|
|
524
|
+
function extractCollectionAliases(query) {
|
|
525
|
+
const aliasesById = /* @__PURE__ */ new Map();
|
|
526
|
+
function recordAlias(source) {
|
|
527
|
+
if (!source) return;
|
|
528
|
+
if (source.type === `collectionRef`) {
|
|
529
|
+
const { id } = source.collection;
|
|
530
|
+
const existing = aliasesById.get(id);
|
|
531
|
+
if (existing) {
|
|
532
|
+
existing.add(source.alias);
|
|
533
|
+
} else {
|
|
534
|
+
aliasesById.set(id, /* @__PURE__ */ new Set([source.alias]));
|
|
535
|
+
}
|
|
536
|
+
} else if (source.type === `queryRef`) {
|
|
537
|
+
traverse(source.query);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function traverse(q) {
|
|
541
|
+
if (!q) return;
|
|
542
|
+
recordAlias(q.from);
|
|
543
|
+
if (q.join) {
|
|
544
|
+
for (const joinClause of q.join) {
|
|
545
|
+
recordAlias(joinClause.from);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
traverse(query);
|
|
550
|
+
return aliasesById;
|
|
551
|
+
}
|
|
318
552
|
function accumulateChanges(acc, [[key, tupleData], multiplicity]) {
|
|
319
553
|
const [value, orderByIndex] = tupleData;
|
|
320
554
|
const changes = acc.get(key) || {
|