@tanstack/db 0.4.8 → 0.4.10

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.
Files changed (134) hide show
  1. package/dist/cjs/collection/events.cjs +9 -51
  2. package/dist/cjs/collection/events.cjs.map +1 -1
  3. package/dist/cjs/collection/events.d.cts +18 -7
  4. package/dist/cjs/collection/index.cjs +9 -12
  5. package/dist/cjs/collection/index.cjs.map +1 -1
  6. package/dist/cjs/collection/index.d.cts +13 -14
  7. package/dist/cjs/collection/subscription.cjs +62 -6
  8. package/dist/cjs/collection/subscription.cjs.map +1 -1
  9. package/dist/cjs/collection/subscription.d.cts +16 -3
  10. package/dist/cjs/collection/sync.cjs +58 -6
  11. package/dist/cjs/collection/sync.cjs.map +1 -1
  12. package/dist/cjs/collection/sync.d.cts +18 -4
  13. package/dist/cjs/errors.cjs +59 -17
  14. package/dist/cjs/errors.cjs.map +1 -1
  15. package/dist/cjs/errors.d.cts +44 -8
  16. package/dist/cjs/event-emitter.cjs +94 -0
  17. package/dist/cjs/event-emitter.cjs.map +1 -0
  18. package/dist/cjs/event-emitter.d.cts +45 -0
  19. package/dist/cjs/index.cjs +9 -4
  20. package/dist/cjs/index.cjs.map +1 -1
  21. package/dist/cjs/local-only.cjs.map +1 -1
  22. package/dist/cjs/local-only.d.cts +2 -5
  23. package/dist/cjs/query/builder/types.d.cts +1 -1
  24. package/dist/cjs/query/compiler/index.cjs +46 -19
  25. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  26. package/dist/cjs/query/compiler/index.d.cts +35 -9
  27. package/dist/cjs/query/compiler/joins.cjs +91 -66
  28. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/joins.d.cts +6 -3
  30. package/dist/cjs/query/compiler/order-by.cjs +20 -4
  31. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  32. package/dist/cjs/query/compiler/order-by.d.cts +3 -1
  33. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  34. package/dist/cjs/query/compiler/types.d.cts +4 -0
  35. package/dist/cjs/query/index.d.cts +1 -0
  36. package/dist/cjs/query/live/collection-config-builder.cjs +306 -46
  37. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  38. package/dist/cjs/query/live/collection-config-builder.d.cts +97 -9
  39. package/dist/cjs/query/live/collection-registry.cjs +16 -0
  40. package/dist/cjs/query/live/collection-registry.cjs.map +1 -0
  41. package/dist/cjs/query/live/collection-registry.d.cts +26 -0
  42. package/dist/cjs/query/live/collection-subscriber.cjs +86 -58
  43. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  44. package/dist/cjs/query/live/collection-subscriber.d.cts +5 -7
  45. package/dist/cjs/query/live-query-collection.cjs +11 -5
  46. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  47. package/dist/cjs/query/live-query-collection.d.cts +12 -5
  48. package/dist/cjs/query/optimizer.cjs +44 -7
  49. package/dist/cjs/query/optimizer.cjs.map +1 -1
  50. package/dist/cjs/query/optimizer.d.cts +4 -4
  51. package/dist/cjs/scheduler.cjs +137 -0
  52. package/dist/cjs/scheduler.cjs.map +1 -0
  53. package/dist/cjs/scheduler.d.cts +56 -0
  54. package/dist/cjs/transactions.cjs +7 -1
  55. package/dist/cjs/transactions.cjs.map +1 -1
  56. package/dist/cjs/types.d.cts +82 -11
  57. package/dist/esm/collection/events.d.ts +18 -7
  58. package/dist/esm/collection/events.js +9 -51
  59. package/dist/esm/collection/events.js.map +1 -1
  60. package/dist/esm/collection/index.d.ts +13 -14
  61. package/dist/esm/collection/index.js +9 -12
  62. package/dist/esm/collection/index.js.map +1 -1
  63. package/dist/esm/collection/subscription.d.ts +16 -3
  64. package/dist/esm/collection/subscription.js +62 -6
  65. package/dist/esm/collection/subscription.js.map +1 -1
  66. package/dist/esm/collection/sync.d.ts +18 -4
  67. package/dist/esm/collection/sync.js +59 -7
  68. package/dist/esm/collection/sync.js.map +1 -1
  69. package/dist/esm/errors.d.ts +44 -8
  70. package/dist/esm/errors.js +60 -18
  71. package/dist/esm/errors.js.map +1 -1
  72. package/dist/esm/event-emitter.d.ts +45 -0
  73. package/dist/esm/event-emitter.js +94 -0
  74. package/dist/esm/event-emitter.js.map +1 -0
  75. package/dist/esm/index.js +10 -5
  76. package/dist/esm/local-only.d.ts +2 -5
  77. package/dist/esm/local-only.js.map +1 -1
  78. package/dist/esm/query/builder/types.d.ts +1 -1
  79. package/dist/esm/query/compiler/index.d.ts +35 -9
  80. package/dist/esm/query/compiler/index.js +46 -19
  81. package/dist/esm/query/compiler/index.js.map +1 -1
  82. package/dist/esm/query/compiler/joins.d.ts +6 -3
  83. package/dist/esm/query/compiler/joins.js +93 -68
  84. package/dist/esm/query/compiler/joins.js.map +1 -1
  85. package/dist/esm/query/compiler/order-by.d.ts +3 -1
  86. package/dist/esm/query/compiler/order-by.js +20 -4
  87. package/dist/esm/query/compiler/order-by.js.map +1 -1
  88. package/dist/esm/query/compiler/select.js.map +1 -1
  89. package/dist/esm/query/compiler/types.d.ts +4 -0
  90. package/dist/esm/query/index.d.ts +1 -0
  91. package/dist/esm/query/live/collection-config-builder.d.ts +97 -9
  92. package/dist/esm/query/live/collection-config-builder.js +306 -46
  93. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  94. package/dist/esm/query/live/collection-registry.d.ts +26 -0
  95. package/dist/esm/query/live/collection-registry.js +16 -0
  96. package/dist/esm/query/live/collection-registry.js.map +1 -0
  97. package/dist/esm/query/live/collection-subscriber.d.ts +5 -7
  98. package/dist/esm/query/live/collection-subscriber.js +86 -58
  99. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  100. package/dist/esm/query/live-query-collection.d.ts +12 -5
  101. package/dist/esm/query/live-query-collection.js +11 -5
  102. package/dist/esm/query/live-query-collection.js.map +1 -1
  103. package/dist/esm/query/optimizer.d.ts +4 -4
  104. package/dist/esm/query/optimizer.js +44 -7
  105. package/dist/esm/query/optimizer.js.map +1 -1
  106. package/dist/esm/scheduler.d.ts +56 -0
  107. package/dist/esm/scheduler.js +137 -0
  108. package/dist/esm/scheduler.js.map +1 -0
  109. package/dist/esm/transactions.js +7 -1
  110. package/dist/esm/transactions.js.map +1 -1
  111. package/dist/esm/types.d.ts +82 -11
  112. package/package.json +2 -2
  113. package/src/collection/events.ts +25 -74
  114. package/src/collection/index.ts +15 -19
  115. package/src/collection/subscription.ts +88 -6
  116. package/src/collection/sync.ts +81 -9
  117. package/src/errors.ts +91 -13
  118. package/src/event-emitter.ts +118 -0
  119. package/src/local-only.ts +5 -12
  120. package/src/query/builder/types.ts +1 -1
  121. package/src/query/compiler/index.ts +124 -33
  122. package/src/query/compiler/joins.ts +187 -128
  123. package/src/query/compiler/order-by.ts +30 -2
  124. package/src/query/compiler/select.ts +2 -3
  125. package/src/query/compiler/types.ts +5 -0
  126. package/src/query/index.ts +1 -0
  127. package/src/query/live/collection-config-builder.ts +501 -60
  128. package/src/query/live/collection-registry.ts +47 -0
  129. package/src/query/live/collection-subscriber.ts +137 -105
  130. package/src/query/live-query-collection.ts +47 -18
  131. package/src/query/optimizer.ts +85 -15
  132. package/src/scheduler.ts +198 -0
  133. package/src/transactions.ts +12 -1
  134. package/src/types.ts +93 -11
@@ -4,27 +4,53 @@ import { OrderByOptimizationInfo } from './order-by.js';
4
4
  import { LazyCollectionCallbacks } from './joins.js';
5
5
  import { Collection } from '../../collection/index.js';
6
6
  import { KeyedStream, ResultStream } from '../../types.js';
7
- import { QueryCache, QueryMapping } from './types.js';
7
+ import { QueryCache, QueryMapping, WindowOptions } from './types.js';
8
+ export type { WindowOptions } from './types.js';
8
9
  /**
9
- * Result of query compilation including both the pipeline and collection-specific WHERE clauses
10
+ * Result of query compilation including both the pipeline and source-specific WHERE clauses
10
11
  */
11
12
  export interface CompilationResult {
12
13
  /** The ID of the main collection */
13
14
  collectionId: string;
14
- /** The compiled query pipeline */
15
+ /** The compiled query pipeline (D2 stream) */
15
16
  pipeline: ResultStream;
16
- /** Map of collection aliases to their WHERE clauses for index optimization */
17
- collectionWhereClauses: Map<string, BasicExpression<boolean>>;
17
+ /** Map of source aliases to their WHERE clauses for index optimization */
18
+ sourceWhereClauses: Map<string, BasicExpression<boolean>>;
19
+ /**
20
+ * Maps each source alias to its collection ID. Enables per-alias subscriptions for self-joins.
21
+ * Example: `{ employee: 'employees-col-id', manager: 'employees-col-id' }`
22
+ */
23
+ aliasToCollectionId: Record<string, string>;
24
+ /**
25
+ * Flattened mapping from outer alias to innermost alias for subqueries.
26
+ * Always provides one-hop lookups, never recursive chains.
27
+ *
28
+ * Example: `{ activeUser: 'user' }` when `.from({ activeUser: subquery })`
29
+ * where the subquery uses `.from({ user: collection })`.
30
+ *
31
+ * For deeply nested subqueries, the mapping goes directly to the innermost alias:
32
+ * `{ author: 'user' }` (not `{ author: 'activeUser' }`), so `aliasRemapping[alias]`
33
+ * always resolves in a single lookup.
34
+ *
35
+ * Used to resolve subscriptions during lazy loading when join aliases differ from
36
+ * the inner aliases where collection subscriptions were created.
37
+ */
38
+ aliasRemapping: Record<string, string>;
18
39
  }
19
40
  /**
20
- * Compiles a query2 IR into a D2 pipeline
41
+ * Compiles a query IR into a D2 pipeline
21
42
  * @param rawQuery The query IR to compile
22
- * @param inputs Mapping of collection names to input streams
43
+ * @param inputs Mapping of source aliases to input streams (e.g., `{ employee: input1, manager: input2 }`)
44
+ * @param collections Mapping of collection IDs to Collection instances
45
+ * @param subscriptions Mapping of source aliases to CollectionSubscription instances
46
+ * @param callbacks Mapping of source aliases to lazy loading callbacks
47
+ * @param lazySources Set of source aliases that should load data lazily
48
+ * @param optimizableOrderByCollections Map of collection IDs to order-by optimization info
23
49
  * @param cache Optional cache for compiled subqueries (used internally for recursion)
24
50
  * @param queryMapping Optional mapping from optimized queries to original queries
25
- * @returns A CompilationResult with the pipeline and collection WHERE clauses
51
+ * @returns A CompilationResult with the pipeline, source WHERE clauses, and alias metadata
26
52
  */
27
- export declare function compileQuery(rawQuery: QueryIR, inputs: Record<string, KeyedStream>, collections: Record<string, Collection<any, any, any, any, any>>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazyCollections: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, cache?: QueryCache, queryMapping?: QueryMapping): CompilationResult;
53
+ export declare function compileQuery(rawQuery: QueryIR, inputs: Record<string, KeyedStream>, collections: Record<string, Collection<any, any, any, any, any>>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazySources: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, cache?: QueryCache, queryMapping?: QueryMapping): CompilationResult;
28
54
  /**
29
55
  * Follows the given reference in a query
30
56
  * until its finds the root field the reference points to.
@@ -6,32 +6,36 @@ const autoIndex = require("../../indexes/auto-index.cjs");
6
6
  const ir = require("../ir.cjs");
7
7
  const functions = require("../builder/functions.cjs");
8
8
  const evaluators = require("./evaluators.cjs");
9
- function processJoins(pipeline, joinClauses, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery, onCompileSubquery) {
9
+ function processJoins(pipeline, joinClauses, sources, mainCollectionId, mainSource, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, rawQuery, onCompileSubquery, aliasToCollectionId, aliasRemapping) {
10
10
  let resultPipeline = pipeline;
11
11
  for (const joinClause of joinClauses) {
12
12
  resultPipeline = processJoin(
13
13
  resultPipeline,
14
14
  joinClause,
15
- tables,
16
- mainTableId,
17
- mainTableAlias,
15
+ sources,
16
+ mainCollectionId,
17
+ mainSource,
18
18
  allInputs,
19
19
  cache,
20
20
  queryMapping,
21
21
  collections,
22
22
  subscriptions,
23
23
  callbacks,
24
- lazyCollections,
24
+ lazySources,
25
25
  optimizableOrderByCollections,
26
+ setWindowFn,
26
27
  rawQuery,
27
- onCompileSubquery
28
+ onCompileSubquery,
29
+ aliasToCollectionId,
30
+ aliasRemapping
28
31
  );
29
32
  }
30
33
  return resultPipeline;
31
34
  }
32
- function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery, onCompileSubquery) {
35
+ function processJoin(pipeline, joinClause, sources, mainCollectionId, mainSource, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, rawQuery, onCompileSubquery, aliasToCollectionId, aliasRemapping) {
36
+ const isCollectionRef = joinClause.from.type === `collectionRef`;
33
37
  const {
34
- alias: joinedTableAlias,
38
+ alias: joinedSource,
35
39
  input: joinedInput,
36
40
  collectionId: joinedCollectionId
37
41
  } = processJoinSource(
@@ -40,32 +44,38 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
40
44
  collections,
41
45
  subscriptions,
42
46
  callbacks,
43
- lazyCollections,
47
+ lazySources,
44
48
  optimizableOrderByCollections,
49
+ setWindowFn,
45
50
  cache,
46
51
  queryMapping,
47
- onCompileSubquery
52
+ onCompileSubquery,
53
+ aliasToCollectionId,
54
+ aliasRemapping
48
55
  );
49
- tables[joinedTableAlias] = joinedInput;
50
- const mainCollection = collections[mainTableId];
56
+ sources[joinedSource] = joinedInput;
57
+ if (isCollectionRef) {
58
+ aliasToCollectionId[joinedSource] = joinedCollectionId;
59
+ }
60
+ const mainCollection = collections[mainCollectionId];
51
61
  const joinedCollection = collections[joinedCollectionId];
52
62
  if (!mainCollection) {
53
- throw new errors.JoinCollectionNotFoundError(mainTableId);
63
+ throw new errors.JoinCollectionNotFoundError(mainCollectionId);
54
64
  }
55
65
  if (!joinedCollection) {
56
66
  throw new errors.JoinCollectionNotFoundError(joinedCollectionId);
57
67
  }
58
- const { activeCollection, lazyCollection } = getActiveAndLazyCollections(
68
+ const { activeSource, lazySource } = getActiveAndLazySources(
59
69
  joinClause.type,
60
70
  mainCollection,
61
71
  joinedCollection
62
72
  );
63
- const availableTableAliases = Object.keys(tables);
73
+ const availableSources = Object.keys(sources);
64
74
  const { mainExpr, joinedExpr } = analyzeJoinExpressions(
65
75
  joinClause.left,
66
76
  joinClause.right,
67
- availableTableAliases,
68
- joinedTableAlias
77
+ availableSources,
78
+ joinedSource
69
79
  );
70
80
  const compiledMainExpr = evaluators.compileExpression(mainExpr);
71
81
  const compiledJoinedExpr = evaluators.compileExpression(joinedExpr);
@@ -77,7 +87,7 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
77
87
  );
78
88
  let joinedPipeline = joinedInput.pipe(
79
89
  dbIvm.map(([currentKey, row]) => {
80
- const namespacedRow = { [joinedTableAlias]: row };
90
+ const namespacedRow = { [joinedSource]: row };
81
91
  const joinedKey = compiledJoinedExpr(namespacedRow);
82
92
  return [joinedKey, [currentKey, namespacedRow]];
83
93
  })
@@ -85,18 +95,19 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
85
95
  if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {
86
96
  throw new errors.UnsupportedJoinTypeError(joinClause.type);
87
97
  }
88
- if (activeCollection) {
89
- const lazyFrom = activeCollection === `main` ? joinClause.from : rawQuery.from;
98
+ if (activeSource) {
99
+ const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from;
90
100
  const limitedSubquery = lazyFrom.type === `queryRef` && (lazyFrom.query.limit || lazyFrom.query.offset);
91
101
  const hasComputedJoinExpr = mainExpr.type === `func` || joinedExpr.type === `func`;
92
102
  if (!limitedSubquery && !hasComputedJoinExpr) {
93
- lazyCollections.add(lazyCollection.id);
94
- const activePipeline = activeCollection === `main` ? mainPipeline : joinedPipeline;
95
- const lazyCollectionJoinExpr = activeCollection === `main` ? joinedExpr : mainExpr;
103
+ const lazyAlias = activeSource === `main` ? joinedSource : mainSource;
104
+ lazySources.add(lazyAlias);
105
+ const activePipeline = activeSource === `main` ? mainPipeline : joinedPipeline;
106
+ const lazySourceJoinExpr = activeSource === `main` ? joinedExpr : mainExpr;
96
107
  const followRefResult = ir.followRef(
97
108
  rawQuery,
98
- lazyCollectionJoinExpr,
99
- lazyCollection
109
+ lazySourceJoinExpr,
110
+ lazySource
100
111
  );
101
112
  const followRefCollection = followRefResult.collection;
102
113
  const fieldName = followRefResult.path[0];
@@ -109,27 +120,31 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
109
120
  }
110
121
  const activePipelineWithLoading = activePipeline.pipe(
111
122
  dbIvm.tap((data) => {
112
- const lazyCollectionSubscription = subscriptions[lazyCollection.id];
113
- if (!lazyCollectionSubscription) {
114
- throw new Error(
115
- `Internal error: subscription for collection is missing in join pipeline. Make sure the live query collection sets the subscription before running the pipeline.`
123
+ const resolvedAlias = aliasRemapping[lazyAlias] || lazyAlias;
124
+ const lazySourceSubscription = subscriptions[resolvedAlias];
125
+ if (!lazySourceSubscription) {
126
+ throw new errors.SubscriptionNotFoundError(
127
+ resolvedAlias,
128
+ lazyAlias,
129
+ lazySource.id,
130
+ Object.keys(subscriptions)
116
131
  );
117
132
  }
118
- if (lazyCollectionSubscription.hasLoadedInitialState()) {
133
+ if (lazySourceSubscription.hasLoadedInitialState()) {
119
134
  return;
120
135
  }
121
136
  const joinKeys = data.getInner().map(([[joinKey]]) => joinKey);
122
137
  const lazyJoinRef = new ir.PropRef(followRefResult.path);
123
- const loaded = lazyCollectionSubscription.requestSnapshot({
138
+ const loaded = lazySourceSubscription.requestSnapshot({
124
139
  where: functions.inArray(lazyJoinRef, joinKeys),
125
140
  optimizedOnly: true
126
141
  });
127
142
  if (!loaded) {
128
- lazyCollectionSubscription.requestSnapshot();
143
+ lazySourceSubscription.requestSnapshot();
129
144
  }
130
145
  })
131
146
  );
132
- if (activeCollection === `main`) {
147
+ if (activeSource === `main`) {
133
148
  mainPipeline = activePipelineWithLoading;
134
149
  } else {
135
150
  joinedPipeline = activePipelineWithLoading;
@@ -138,61 +153,65 @@ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias,
138
153
  }
139
154
  return mainPipeline.pipe(
140
155
  dbIvm.join(joinedPipeline, joinClause.type),
141
- dbIvm.consolidate(),
142
156
  processJoinResults(joinClause.type)
143
157
  );
144
158
  }
145
- function analyzeJoinExpressions(left, right, allAvailableTableAliases, joinedTableAlias) {
146
- const availableTableAliases = allAvailableTableAliases.filter(
147
- (alias) => alias !== joinedTableAlias
159
+ function analyzeJoinExpressions(left, right, allAvailableSourceAliases, joinedSource) {
160
+ const availableSources = allAvailableSourceAliases.filter(
161
+ (alias) => alias !== joinedSource
148
162
  );
149
- const leftTableAlias = getTableAliasFromExpression(left);
150
- const rightTableAlias = getTableAliasFromExpression(right);
151
- if (leftTableAlias && availableTableAliases.includes(leftTableAlias) && rightTableAlias === joinedTableAlias) {
163
+ const leftSourceAlias = getSourceAliasFromExpression(left);
164
+ const rightSourceAlias = getSourceAliasFromExpression(right);
165
+ if (leftSourceAlias && availableSources.includes(leftSourceAlias) && rightSourceAlias === joinedSource) {
152
166
  return { mainExpr: left, joinedExpr: right };
153
167
  }
154
- if (leftTableAlias === joinedTableAlias && rightTableAlias && availableTableAliases.includes(rightTableAlias)) {
168
+ if (leftSourceAlias === joinedSource && rightSourceAlias && availableSources.includes(rightSourceAlias)) {
155
169
  return { mainExpr: right, joinedExpr: left };
156
170
  }
157
- if (!leftTableAlias || !rightTableAlias) {
158
- throw new errors.InvalidJoinConditionTableMismatchError();
171
+ if (!leftSourceAlias || !rightSourceAlias) {
172
+ throw new errors.InvalidJoinConditionSourceMismatchError();
159
173
  }
160
- if (leftTableAlias === rightTableAlias) {
161
- throw new errors.InvalidJoinConditionSameTableError(leftTableAlias);
174
+ if (leftSourceAlias === rightSourceAlias) {
175
+ throw new errors.InvalidJoinConditionSameSourceError(leftSourceAlias);
162
176
  }
163
- if (!availableTableAliases.includes(leftTableAlias)) {
164
- throw new errors.InvalidJoinConditionLeftTableError(leftTableAlias);
177
+ if (!availableSources.includes(leftSourceAlias)) {
178
+ throw new errors.InvalidJoinConditionLeftSourceError(leftSourceAlias);
165
179
  }
166
- if (rightTableAlias !== joinedTableAlias) {
167
- throw new errors.InvalidJoinConditionRightTableError(joinedTableAlias);
180
+ if (rightSourceAlias !== joinedSource) {
181
+ throw new errors.InvalidJoinConditionRightSourceError(joinedSource);
168
182
  }
169
183
  throw new errors.InvalidJoinCondition();
170
184
  }
171
- function getTableAliasFromExpression(expr) {
185
+ function getSourceAliasFromExpression(expr) {
172
186
  switch (expr.type) {
173
187
  case `ref`:
174
188
  return expr.path[0] || null;
175
189
  case `func`: {
176
- const tableAliases = /* @__PURE__ */ new Set();
190
+ const sourceAliases = /* @__PURE__ */ new Set();
177
191
  for (const arg of expr.args) {
178
- const alias = getTableAliasFromExpression(arg);
192
+ const alias = getSourceAliasFromExpression(arg);
179
193
  if (alias) {
180
- tableAliases.add(alias);
194
+ sourceAliases.add(alias);
181
195
  }
182
196
  }
183
- return tableAliases.size === 1 ? Array.from(tableAliases)[0] : null;
197
+ return sourceAliases.size === 1 ? Array.from(sourceAliases)[0] : null;
184
198
  }
185
199
  default:
186
200
  return null;
187
201
  }
188
202
  }
189
- function processJoinSource(from, allInputs, collections, subscriptions, callbacks, lazyCollections, optimizableOrderByCollections, cache, queryMapping, onCompileSubquery) {
203
+ function processJoinSource(from, allInputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache, queryMapping, onCompileSubquery, aliasToCollectionId, aliasRemapping) {
190
204
  switch (from.type) {
191
205
  case `collectionRef`: {
192
- const input = allInputs[from.collection.id];
206
+ const input = allInputs[from.alias];
193
207
  if (!input) {
194
- throw new errors.CollectionInputNotFoundError(from.collection.id);
208
+ throw new errors.CollectionInputNotFoundError(
209
+ from.alias,
210
+ from.collection.id,
211
+ Object.keys(allInputs)
212
+ );
195
213
  }
214
+ aliasToCollectionId[from.alias] = from.collection.id;
196
215
  return { alias: from.alias, input, collectionId: from.collection.id };
197
216
  }
198
217
  case `queryRef`: {
@@ -203,11 +222,20 @@ function processJoinSource(from, allInputs, collections, subscriptions, callback
203
222
  collections,
204
223
  subscriptions,
205
224
  callbacks,
206
- lazyCollections,
225
+ lazySources,
207
226
  optimizableOrderByCollections,
227
+ setWindowFn,
208
228
  cache,
209
229
  queryMapping
210
230
  );
231
+ Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId);
232
+ Object.assign(aliasRemapping, subQueryResult.aliasRemapping);
233
+ const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(
234
+ (alias) => subQueryResult.aliasToCollectionId[alias] === subQueryResult.collectionId
235
+ );
236
+ if (innerAlias && innerAlias !== from.alias) {
237
+ aliasRemapping[from.alias] = innerAlias;
238
+ }
211
239
  const subQueryInput = subQueryResult.pipeline;
212
240
  const extractedInput = subQueryInput.pipe(
213
241
  dbIvm.map((data) => {
@@ -263,19 +291,16 @@ function processJoinResults(joinType) {
263
291
  );
264
292
  };
265
293
  }
266
- function getActiveAndLazyCollections(joinType, leftCollection, rightCollection) {
267
- if (leftCollection.id === rightCollection.id) {
268
- return { activeCollection: void 0, lazyCollection: void 0 };
269
- }
294
+ function getActiveAndLazySources(joinType, leftCollection, rightCollection) {
270
295
  switch (joinType) {
271
296
  case `left`:
272
- return { activeCollection: `main`, lazyCollection: rightCollection };
297
+ return { activeSource: `main`, lazySource: rightCollection };
273
298
  case `right`:
274
- return { activeCollection: `joined`, lazyCollection: leftCollection };
299
+ return { activeSource: `joined`, lazySource: leftCollection };
275
300
  case `inner`:
276
- return leftCollection.size < rightCollection.size ? { activeCollection: `main`, lazyCollection: rightCollection } : { activeCollection: `joined`, lazyCollection: leftCollection };
301
+ return leftCollection.size < rightCollection.size ? { activeSource: `main`, lazySource: rightCollection } : { activeSource: `joined`, lazySource: leftCollection };
277
302
  default:
278
- return { activeCollection: void 0, lazyCollection: void 0 };
303
+ return { activeSource: void 0, lazySource: void 0 };
279
304
  }
280
305
  }
281
306
  exports.processJoins = processJoins;
@@ -1 +1 @@
1
- {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n tap,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftTableError,\n InvalidJoinConditionRightTableError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { PropRef, followRef } from \"../ir.js\"\nimport { inArray } from \"../builder/functions.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { CompileQueryFn } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection/index.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\nimport type { CollectionSubscription } from \"../../collection/subscription.js\"\n\nexport type LoadKeysFn = (key: Set<string | number>) => void\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n subscriptions,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery,\n onCompileSubquery\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const {\n alias: joinedTableAlias,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping,\n onCompileSubquery\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n const mainCollection = collections[mainTableId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainTableId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeCollection, lazyCollection } = getActiveAndLazyCollections(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which table each expression refers to and swap if necessary\n const availableTableAliases = Object.keys(tables)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableTableAliases,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeCollection) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom =\n activeCollection === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n // If join expressions contain computed values (like concat functions)\n // we don't optimize the join because we don't have an index over the computed values\n const hasComputedJoinExpr =\n mainExpr.type === `func` || joinedExpr.type === `func`\n\n if (!limitedSubquery && !hasComputedJoinExpr) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy collection as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which collections are lazy collections\n lazyCollections.add(lazyCollection.id)\n\n const activePipeline =\n activeCollection === `main` ? mainPipeline : joinedPipeline\n\n const lazyCollectionJoinExpr =\n activeCollection === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazyCollectionJoinExpr,\n lazyCollection\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection\n )\n }\n\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n const lazyCollectionSubscription = subscriptions[lazyCollection.id]\n\n if (!lazyCollectionSubscription) {\n throw new Error(\n `Internal error: subscription for collection is missing in join pipeline. Make sure the live query collection sets the subscription before running the pipeline.`\n )\n }\n\n if (lazyCollectionSubscription.hasLoadedInitialState()) {\n // Entire state was already loaded because we deoptimized the join\n return\n }\n\n const joinKeys = data.getInner().map(([[joinKey]]) => joinKey)\n const lazyJoinRef = new PropRef(followRefResult.path)\n const loaded = lazyCollectionSubscription.requestSnapshot({\n where: inArray(lazyJoinRef, joinKeys),\n optimizedOnly: true,\n })\n\n if (!loaded) {\n // Snapshot wasn't sent because it could not be loaded from the indexes\n lazyCollectionSubscription.requestSnapshot()\n }\n })\n )\n\n if (activeCollection === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (available table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableTableAliases: Array<string>,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined table alias from the available table aliases\n const availableTableAliases = allAvailableTableAliases.filter(\n (alias) => alias !== joinedTableAlias\n )\n\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to an available table and right refers to joined table, keep as is\n if (\n leftTableAlias &&\n availableTableAliases.includes(leftTableAlias) &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to an available table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias &&\n availableTableAliases.includes(rightTableAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n // For backward compatibility, use the first available table alias in error message\n throw new InvalidJoinConditionTableMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias)\n }\n\n // Left side must refer to an available table\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable table, but just in case, but could happen with the IR\n if (!availableTableAliases.includes(leftTableAlias)) {\n throw new InvalidJoinConditionLeftTableError(leftTableAlias)\n }\n\n // Right side must refer to the joined table\n if (rightTableAlias !== joinedTableAlias) {\n throw new InvalidJoinConditionRightTableError(joinedTableAlias)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileQueryFn\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = onCompileSubquery(\n originalQuery,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main table (i.e. left collection) or the joined table (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazyCollections(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeCollection: `main` | `joined`; lazyCollection: Collection }\n | { activeCollection: undefined; lazyCollection: undefined } {\n if (leftCollection.id === rightCollection.id) {\n // We can't apply this optimization if there's only one collection\n // because `liveQueryCollection` will detect that the collection is lazy\n // and treat it lazily (because the collection is shared)\n // and thus it will not load any keys because both sides of the join\n // will be handled lazily\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n\n switch (joinType) {\n case `left`:\n return { activeCollection: `main`, lazyCollection: rightCollection }\n case `right`:\n return { activeCollection: `joined`, lazyCollection: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeCollection: `main`, lazyCollection: rightCollection }\n : { activeCollection: `joined`, lazyCollection: leftCollection }\n default:\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","UnsupportedJoinTypeError","followRef","ensureIndexForField","tap","PropRef","inArray","joinOperator","consolidate","InvalidJoinConditionTableMismatchError","InvalidJoinConditionSameTableError","InvalidJoinConditionLeftTableError","InvalidJoinConditionRightTableError","InvalidJoinCondition","CollectionInputNotFoundError","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
1
+ {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import { filter, join as joinOperator, map, tap } from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftSourceError,\n InvalidJoinConditionRightSourceError,\n InvalidJoinConditionSameSourceError,\n InvalidJoinConditionSourceMismatchError,\n JoinCollectionNotFoundError,\n SubscriptionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { PropRef, followRef } from \"../ir.js\"\nimport { inArray } from \"../builder/functions.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { CompileQueryFn } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection/index.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping, WindowOptions } from \"./types.js\"\nimport type { CollectionSubscription } from \"../../collection/subscription.js\"\n\n/** Function type for loading specific keys into a lazy collection */\nexport type LoadKeysFn = (key: Set<string | number>) => void\n\n/** Callbacks for managing lazy-loaded collections in optimized joins */\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses, applying lazy loading optimizations and maintaining\n * alias tracking for per-alias subscriptions (enables self-joins).\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n sources,\n mainCollectionId,\n mainSource,\n allInputs,\n cache,\n queryMapping,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n rawQuery,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause with lazy loading optimization.\n * For LEFT/RIGHT/INNER joins, marks one side as \"lazy\" (loads on-demand based on join keys).\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>\n): NamespacedAndKeyedStream {\n const isCollectionRef = joinClause.from.type === `collectionRef`\n\n // Get the joined source alias and input stream\n const {\n alias: joinedSource,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping\n )\n\n // Add the joined source to the sources map\n sources[joinedSource] = joinedInput\n if (isCollectionRef) {\n // Only direct collection references form new alias bindings. Subquery\n // aliases reuse the mapping returned from the recursive compilation above.\n aliasToCollectionId[joinedSource] = joinedCollectionId\n }\n\n const mainCollection = collections[mainCollectionId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainCollectionId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeSource, lazySource } = getActiveAndLazySources(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which source each expression refers to and swap if necessary\n const availableSources = Object.keys(sources)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableSources,\n joinedSource\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main source expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedSource]: row }\n\n // Extract the join key from the joined source expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeSource) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n\n // If join expressions contain computed values (like concat functions)\n // we don't optimize the join because we don't have an index over the computed values\n const hasComputedJoinExpr =\n mainExpr.type === `func` || joinedExpr.type === `func`\n\n if (!limitedSubquery && !hasComputedJoinExpr) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy source alias as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which source aliases should load data lazily (not initially)\n const lazyAlias = activeSource === `main` ? joinedSource : mainSource\n lazySources.add(lazyAlias)\n\n const activePipeline =\n activeSource === `main` ? mainPipeline : joinedPipeline\n\n const lazySourceJoinExpr =\n activeSource === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const followRefResult = followRef(\n rawQuery,\n lazySourceJoinExpr,\n lazySource\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection\n )\n }\n\n // Set up lazy loading: intercept active side's stream and dynamically load\n // matching rows from lazy side based on join keys.\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n // Find the subscription for lazy loading.\n // Subscriptions are keyed by the innermost alias (where the collection subscription\n // was actually created). For subqueries, the join alias may differ from the inner alias.\n // aliasRemapping provides a flattened one-hop lookup from outer → innermost alias.\n // Example: .join({ activeUser: subquery }) where subquery uses .from({ user: collection })\n // → aliasRemapping['activeUser'] = 'user' (always maps directly to innermost, never recursive)\n const resolvedAlias = aliasRemapping[lazyAlias] || lazyAlias\n const lazySourceSubscription = subscriptions[resolvedAlias]\n\n if (!lazySourceSubscription) {\n throw new SubscriptionNotFoundError(\n resolvedAlias,\n lazyAlias,\n lazySource.id,\n Object.keys(subscriptions)\n )\n }\n\n if (lazySourceSubscription.hasLoadedInitialState()) {\n // Entire state was already loaded because we deoptimized the join\n return\n }\n\n // Request filtered snapshot from lazy collection for matching join keys\n const joinKeys = data.getInner().map(([[joinKey]]) => joinKey)\n const lazyJoinRef = new PropRef(followRefResult.path)\n const loaded = lazySourceSubscription.requestSnapshot({\n where: inArray(lazyJoinRef, joinKeys),\n optimizedOnly: true,\n })\n\n if (!loaded) {\n // Snapshot wasn't sent because it could not be loaded from the indexes\n lazySourceSubscription.requestSnapshot()\n }\n })\n )\n\n if (activeSource === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which source\n * and returns them in the correct order (available source expression first, joined source expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableSourceAliases: Array<string>,\n joinedSource: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined source alias from the available source aliases\n const availableSources = allAvailableSourceAliases.filter(\n (alias) => alias !== joinedSource\n )\n\n const leftSourceAlias = getSourceAliasFromExpression(left)\n const rightSourceAlias = getSourceAliasFromExpression(right)\n\n // If left expression refers to an available source and right refers to joined source, keep as is\n if (\n leftSourceAlias &&\n availableSources.includes(leftSourceAlias) &&\n rightSourceAlias === joinedSource\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined source and right refers to an available source, swap them\n if (\n leftSourceAlias === joinedSource &&\n rightSourceAlias &&\n availableSources.includes(rightSourceAlias)\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any source, this is an invalid join\n if (!leftSourceAlias || !rightSourceAlias) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftSourceAlias === rightSourceAlias) {\n throw new InvalidJoinConditionSameSourceError(leftSourceAlias)\n }\n\n // Left side must refer to an available source\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable source, but just in case, but could happen with the IR\n if (!availableSources.includes(leftSourceAlias)) {\n throw new InvalidJoinConditionLeftSourceError(leftSourceAlias)\n }\n\n // Right side must refer to the joined source\n if (rightSourceAlias !== joinedSource) {\n throw new InvalidJoinConditionRightSourceError(joinedSource)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the source alias from a join expression\n */\nfunction getSourceAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the source alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same source\n const sourceAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getSourceAliasFromExpression(arg)\n if (alias) {\n sourceAliases.add(alias)\n }\n }\n // If all arguments refer to the same source, return that source alias\n return sourceAliases.size === 1 ? Array.from(sourceAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any source\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n cache: QueryCache,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.alias]\n if (!input) {\n throw new CollectionInputNotFoundError(\n from.alias,\n from.collection.id,\n Object.keys(allInputs)\n )\n }\n aliasToCollectionId[from.alias] = from.collection.id\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = onCompileSubquery(\n originalQuery,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping\n )\n\n // Pull up alias mappings from subquery to parent scope.\n // This includes both the innermost alias-to-collection mappings AND\n // any existing remappings from nested subquery levels.\n Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)\n Object.assign(aliasRemapping, subQueryResult.aliasRemapping)\n\n // Create a flattened remapping from outer alias to innermost alias.\n // For nested subqueries, this ensures one-hop lookups (not recursive chains).\n //\n // Example with 3-level nesting:\n // Inner: .from({ user: usersCollection })\n // Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user\n // Outer: .join({ author: middleSubquery }, ...) → creates: author → user (not author → activeUser)\n //\n // We search through the PULLED-UP aliasToCollectionId (which contains the\n // innermost 'user' alias), so we always map directly to the deepest level.\n // This means aliasRemapping[lazyAlias] is always a single lookup, never recursive.\n const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(\n (alias) =>\n subQueryResult.aliasToCollectionId[alias] ===\n subQueryResult.collectionId\n )\n if (innerAlias && innerAlias !== from.alias) {\n aliasRemapping[from.alias] = innerAlias\n }\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main source (i.e. left collection) or the joined source (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazySources(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeSource: `main` | `joined`; lazySource: Collection }\n | { activeSource: undefined; lazySource: undefined } {\n // Self-joins can now be optimized since we track lazy loading by source alias\n // rather than collection ID. Each alias has its own subscription and lazy state.\n\n switch (joinType) {\n case `left`:\n return { activeSource: `main`, lazySource: rightCollection }\n case `right`:\n return { activeSource: `joined`, lazySource: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeSource: `main`, lazySource: rightCollection }\n : { activeSource: `joined`, lazySource: leftCollection }\n default:\n return { activeSource: undefined, lazySource: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","UnsupportedJoinTypeError","followRef","ensureIndexForField","tap","SubscriptionNotFoundError","PropRef","inArray","joinOperator","InvalidJoinConditionSourceMismatchError","InvalidJoinConditionSameSourceError","InvalidJoinConditionLeftSourceError","InvalidJoinConditionRightSourceError","InvalidJoinCondition","CollectionInputNotFoundError","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
@@ -3,14 +3,17 @@ import { OrderByOptimizationInfo } from './order-by.js';
3
3
  import { JoinClause, QueryIR } from '../ir.js';
4
4
  import { Collection } from '../../collection/index.js';
5
5
  import { KeyedStream, NamespacedAndKeyedStream } from '../../types.js';
6
- import { QueryCache, QueryMapping } from './types.js';
6
+ import { QueryCache, QueryMapping, WindowOptions } 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 in a query
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>, tables: Record<string, KeyedStream>, mainTableId: string, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping, collections: Record<string, Collection>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazyCollections: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, rawQuery: QueryIR, onCompileSubquery: CompileQueryFn): NamespacedAndKeyedStream;
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>, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, rawQuery: QueryIR, onCompileSubquery: CompileQueryFn, aliasToCollectionId: Record<string, string>, aliasRemapping: Record<string, string>): NamespacedAndKeyedStream;
@@ -7,7 +7,7 @@ const autoIndex = require("../../indexes/auto-index.cjs");
7
7
  const indexOptimization = require("../../utils/index-optimization.cjs");
8
8
  const evaluators = require("./evaluators.cjs");
9
9
  const groupBy = require("./group-by.cjs");
10
- function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collection, optimizableOrderByCollections, limit, offset) {
10
+ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collection, optimizableOrderByCollections, setWindowFn, limit, offset) {
11
11
  const compiledOrderBy = orderByClause.map((clause) => {
12
12
  const clauseWithoutAggregates = groupBy.replaceAggregatesByRefs(
13
13
  clause.expression,
@@ -53,6 +53,7 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
53
53
  return comparison.defaultComparator(a, b);
54
54
  };
55
55
  let setSizeCallback;
56
+ let orderByOptimizationInfo;
56
57
  if (limit && orderByClause.length === 1) {
57
58
  const clause = orderByClause[0];
58
59
  const orderByExpression = clause.expression;
@@ -88,7 +89,9 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
88
89
  clause.compareOptions
89
90
  );
90
91
  if (index && index.supports(`gt`)) {
91
- const orderByOptimizationInfo = {
92
+ const orderByAlias = orderByExpression.path.length > 1 ? String(orderByExpression.path[0]) : rawQuery.from.alias;
93
+ orderByOptimizationInfo = {
94
+ alias: orderByAlias,
92
95
  offset: offset ?? 0,
93
96
  limit,
94
97
  comparator,
@@ -102,7 +105,7 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
102
105
  ...optimizableOrderByCollections[followRefCollection.id],
103
106
  dataNeeded: () => {
104
107
  const size = getSize();
105
- return Math.max(0, limit - size);
108
+ return Math.max(0, orderByOptimizationInfo.limit - size);
106
109
  }
107
110
  };
108
111
  };
@@ -114,7 +117,20 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
114
117
  limit,
115
118
  offset,
116
119
  comparator: compare,
117
- setSizeCallback
120
+ setSizeCallback,
121
+ setWindowFn: (windowFn) => {
122
+ setWindowFn(
123
+ // We wrap the move function such that we update the orderByOptimizationInfo
124
+ // because that is used by the `dataNeeded` callback to determine if we need to load more data
125
+ (options) => {
126
+ windowFn(options);
127
+ if (orderByOptimizationInfo) {
128
+ orderByOptimizationInfo.offset = options.offset ?? orderByOptimizationInfo.offset;
129
+ orderByOptimizationInfo.limit = options.limit ?? orderByOptimizationInfo.limit;
130
+ }
131
+ }
132
+ );
133
+ }
118
134
  })
119
135
  // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
120
136
  );