@tanstack/db 0.5.11 → 0.5.12
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/SortedMap.cjs +40 -26
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -15
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +12 -4
- package/dist/cjs/collection/index.cjs +2 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +5 -2
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +6 -5
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +4 -1
- package/dist/cjs/collection/subscription.cjs +60 -53
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +18 -4
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +9 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/event-emitter.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.cjs +8 -6
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/paced-mutations.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +91 -38
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +6 -2
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/expression-helpers.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/internal.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +19 -2
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.d.cts +32 -1
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/scheduler.cjs.map +1 -1
- package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +43 -5
- package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/cursor.cjs +39 -0
- package/dist/cjs/utils/cursor.cjs.map +1 -0
- package/dist/cjs/utils/cursor.d.cts +18 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/SortedMap.d.ts +10 -15
- package/dist/esm/SortedMap.js +40 -26
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.d.ts +12 -4
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.js +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +6 -3
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +4 -1
- package/dist/esm/collection/state.js +6 -5
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +18 -4
- package/dist/esm/collection/subscription.js +61 -54
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.js +8 -6
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.js.map +1 -1
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/paced-mutations.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +6 -2
- package/dist/esm/query/compiler/order-by.js +91 -38
- 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/expression-helpers.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +30 -15
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/internal.js.map +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/query/predicate-utils.d.ts +32 -1
- package/dist/esm/query/predicate-utils.js +19 -2
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/scheduler.js.map +1 -1
- package/dist/esm/strategies/debounceStrategy.js.map +1 -1
- package/dist/esm/strategies/queueStrategy.js.map +1 -1
- package/dist/esm/strategies/throttleStrategy.js.map +1 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +43 -5
- package/dist/esm/utils/browser-polyfills.js.map +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/cursor.d.ts +18 -0
- package/dist/esm/utils/cursor.js +39 -0
- package/dist/esm/utils/cursor.js.map +1 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +30 -28
- package/src/SortedMap.ts +50 -31
- package/src/collection/change-events.ts +20 -20
- package/src/collection/changes.ts +12 -12
- package/src/collection/events.ts +20 -10
- package/src/collection/index.ts +47 -46
- package/src/collection/indexes.ts +14 -14
- package/src/collection/lifecycle.ts +16 -16
- package/src/collection/mutations.ts +25 -20
- package/src/collection/state.ts +43 -36
- package/src/collection/subscription.ts +114 -83
- package/src/collection/sync.ts +13 -13
- package/src/duplicate-instance-check.ts +1 -1
- package/src/errors.ts +49 -40
- package/src/event-emitter.ts +5 -5
- package/src/index.ts +21 -21
- package/src/indexes/auto-index.ts +11 -11
- package/src/indexes/base-index.ts +13 -13
- package/src/indexes/btree-index.ts +21 -17
- package/src/indexes/index-options.ts +3 -3
- package/src/indexes/lazy-index.ts +8 -8
- package/src/indexes/reverse-index.ts +5 -5
- package/src/local-only.ts +12 -12
- package/src/local-storage.ts +17 -17
- package/src/optimistic-action.ts +5 -5
- package/src/paced-mutations.ts +6 -6
- package/src/proxy.ts +43 -43
- package/src/query/builder/functions.ts +28 -28
- package/src/query/builder/index.ts +22 -22
- package/src/query/builder/ref-proxy.ts +4 -4
- package/src/query/builder/types.ts +8 -8
- package/src/query/compiler/evaluators.ts +9 -9
- package/src/query/compiler/expressions.ts +6 -6
- package/src/query/compiler/group-by.ts +24 -24
- package/src/query/compiler/index.ts +44 -44
- package/src/query/compiler/joins.ts +37 -37
- package/src/query/compiler/order-by.ts +170 -77
- package/src/query/compiler/select.ts +13 -13
- package/src/query/compiler/types.ts +2 -2
- package/src/query/expression-helpers.ts +16 -16
- package/src/query/index.ts +10 -9
- package/src/query/ir.ts +13 -13
- package/src/query/live/collection-config-builder.ts +53 -53
- package/src/query/live/collection-registry.ts +6 -6
- package/src/query/live/collection-subscriber.ts +87 -48
- package/src/query/live/internal.ts +1 -1
- package/src/query/live/types.ts +4 -4
- package/src/query/live-query-collection.ts +15 -15
- package/src/query/optimizer.ts +29 -29
- package/src/query/predicate-utils.ts +105 -50
- package/src/query/subset-dedupe.ts +6 -6
- package/src/scheduler.ts +3 -3
- package/src/strategies/debounceStrategy.ts +6 -6
- package/src/strategies/index.ts +4 -4
- package/src/strategies/queueStrategy.ts +5 -5
- package/src/strategies/throttleStrategy.ts +6 -6
- package/src/strategies/types.ts +2 -2
- package/src/transactions.ts +9 -9
- package/src/types.ts +51 -12
- package/src/utils/array-utils.ts +1 -1
- package/src/utils/browser-polyfills.ts +2 -2
- package/src/utils/btree.ts +22 -22
- package/src/utils/comparison.ts +3 -3
- package/src/utils/cursor.ts +78 -0
- package/src/utils/index-optimization.ts +14 -14
- package/src/utils.ts +4 -4
|
@@ -54,55 +54,108 @@ function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collect
|
|
|
54
54
|
};
|
|
55
55
|
let setSizeCallback;
|
|
56
56
|
let orderByOptimizationInfo;
|
|
57
|
-
if (limit
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (limit) {
|
|
58
|
+
let index;
|
|
59
|
+
let followRefCollection;
|
|
60
|
+
let firstColumnValueExtractor;
|
|
61
|
+
let orderByAlias = rawQuery.from.alias;
|
|
62
|
+
const firstClause = orderByClause[0];
|
|
63
|
+
const firstOrderByExpression = firstClause.expression;
|
|
64
|
+
if (firstOrderByExpression.type === `ref`) {
|
|
61
65
|
const followRefResult = ir.followRef(
|
|
62
66
|
rawQuery,
|
|
63
|
-
|
|
67
|
+
firstOrderByExpression,
|
|
64
68
|
collection
|
|
65
69
|
);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (followRefResult) {
|
|
71
|
+
followRefCollection = followRefResult.collection;
|
|
72
|
+
const fieldName = followRefResult.path[0];
|
|
73
|
+
const compareOpts = buildCompareOptions(
|
|
74
|
+
firstClause,
|
|
75
|
+
followRefCollection
|
|
76
|
+
);
|
|
77
|
+
if (fieldName) {
|
|
78
|
+
autoIndex.ensureIndexForField(
|
|
79
|
+
fieldName,
|
|
80
|
+
followRefResult.path,
|
|
81
|
+
followRefCollection,
|
|
82
|
+
compareOpts,
|
|
83
|
+
compare
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
firstColumnValueExtractor = evaluators.compileExpression(
|
|
87
|
+
new ir.PropRef(followRefResult.path),
|
|
88
|
+
true
|
|
89
|
+
);
|
|
90
|
+
index = indexOptimization.findIndexForField(
|
|
73
91
|
followRefCollection,
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
followRefResult.path,
|
|
93
|
+
compareOpts
|
|
76
94
|
);
|
|
95
|
+
if (!index?.supports(`gt`)) {
|
|
96
|
+
index = void 0;
|
|
97
|
+
}
|
|
98
|
+
orderByAlias = firstOrderByExpression.path.length > 1 ? String(firstOrderByExpression.path[0]) : rawQuery.from.alias;
|
|
77
99
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
}
|
|
101
|
+
if (!firstColumnValueExtractor) ;
|
|
102
|
+
else {
|
|
103
|
+
const allColumnsAreRefs = orderByClause.every(
|
|
104
|
+
(clause) => clause.expression.type === `ref`
|
|
81
105
|
);
|
|
106
|
+
const allColumnExtractors = allColumnsAreRefs ? orderByClause.map((clause) => {
|
|
107
|
+
const refExpr = clause.expression;
|
|
108
|
+
const followResult = ir.followRef(rawQuery, refExpr, collection);
|
|
109
|
+
if (followResult) {
|
|
110
|
+
return evaluators.compileExpression(
|
|
111
|
+
new ir.PropRef(followResult.path),
|
|
112
|
+
true
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return evaluators.compileExpression(
|
|
116
|
+
clause.expression,
|
|
117
|
+
true
|
|
118
|
+
);
|
|
119
|
+
}) : void 0;
|
|
82
120
|
const comparator = (a, b) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
121
|
+
if (orderByClause.length === 1) {
|
|
122
|
+
const extractedA = a ? firstColumnValueExtractor(a) : a;
|
|
123
|
+
const extractedB = b ? firstColumnValueExtractor(b) : b;
|
|
124
|
+
return compare(extractedA, extractedB);
|
|
125
|
+
}
|
|
126
|
+
if (allColumnExtractors) {
|
|
127
|
+
const extractAll = (row) => {
|
|
128
|
+
if (!row) return row;
|
|
129
|
+
return allColumnExtractors.map((extractor) => extractor(row));
|
|
130
|
+
};
|
|
131
|
+
return compare(extractAll(a), extractAll(b));
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
86
134
|
};
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
135
|
+
const rawRowValueExtractor = (row) => {
|
|
136
|
+
if (orderByClause.length === 1) {
|
|
137
|
+
return firstColumnValueExtractor(row);
|
|
138
|
+
}
|
|
139
|
+
if (allColumnExtractors) {
|
|
140
|
+
return allColumnExtractors.map((extractor) => extractor(row));
|
|
141
|
+
}
|
|
142
|
+
return void 0;
|
|
143
|
+
};
|
|
144
|
+
orderByOptimizationInfo = {
|
|
145
|
+
alias: orderByAlias,
|
|
146
|
+
offset: offset ?? 0,
|
|
147
|
+
limit,
|
|
148
|
+
comparator,
|
|
149
|
+
valueExtractorForRawRow: rawRowValueExtractor,
|
|
150
|
+
firstColumnValueExtractor,
|
|
151
|
+
index,
|
|
152
|
+
orderBy: orderByClause
|
|
153
|
+
};
|
|
154
|
+
const targetCollectionId = followRefCollection?.id ?? collection.id;
|
|
155
|
+
optimizableOrderByCollections[targetCollectionId] = orderByOptimizationInfo;
|
|
156
|
+
if (index) {
|
|
104
157
|
setSizeCallback = (getSize) => {
|
|
105
|
-
optimizableOrderByCollections[
|
|
158
|
+
optimizableOrderByCollections[targetCollectionId][`dataNeeded`] = () => {
|
|
106
159
|
const size = getSize();
|
|
107
160
|
return Math.max(0, orderByOptimizationInfo.limit - size);
|
|
108
161
|
};
|
|
@@ -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 { CompareOptions } from \"../builder/types.js\"\nimport type { WindowOptions } from \"./types.js\"\nimport type { CompiledSingleRowExpression } from \"./evaluators.js\"\nimport type { OrderBy, OrderByClause, QueryIR, Select } from \"../ir.js\"\nimport type {\n CollectionLike,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} 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 setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\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\n return {\n compiledExpression: compileExpression(clauseWithoutAggregates),\n compareOptions: buildCompareOptions(clause, collection),\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 = compiledOrderBy[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 = compiledOrderBy[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 let orderByOptimizationInfo: OrderByOptimizationInfo | 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 const compareOpts = buildCompareOptions(clause, followRefCollection)\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n compareOpts,\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,\n followRefResult.path,\n compareOpts\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 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]![`dataNeeded`] =\n () => {\n const size = getSize()\n return Math.max(0, orderByOptimizationInfo!.limit - size)\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 setWindowFn: (\n windowFn: (options: { offset?: number; limit?: number }) => void\n ) => {\n setWindowFn(\n // We wrap the move function such that we update the orderByOptimizationInfo\n // because that is used by the `dataNeeded` callback to determine if we need to load more data\n (options) => {\n windowFn(options)\n if (orderByOptimizationInfo) {\n orderByOptimizationInfo.offset =\n options.offset ?? orderByOptimizationInfo.offset\n orderByOptimizationInfo.limit =\n options.limit ?? orderByOptimizationInfo.limit\n }\n }\n )\n },\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n\n/**\n * Builds a comparison configuration object that uses the values provided in the orderBy clause.\n * If no string sort configuration is provided it defaults to the collection's string sort configuration.\n */\nexport function buildCompareOptions(\n clause: OrderByClause,\n collection: CollectionLike<any, any>\n): CompareOptions {\n if (clause.compareOptions.stringSort !== undefined) {\n return clause.compareOptions\n }\n\n return {\n ...collection.compareOptions,\n direction: clause.compareOptions.direction,\n nulls: clause.compareOptions.nulls,\n }\n}\n"],"names":["replaceAggregatesByRefs","compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","PropRef","findIndexForField","orderByWithFractionalIndex"],"mappings":"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"}
|
|
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 { CompareOptions } from '../builder/types.js'\nimport type { WindowOptions } from './types.js'\nimport type { CompiledSingleRowExpression } from './evaluators.js'\nimport type { OrderBy, OrderByClause, QueryIR, Select } from '../ir.js'\nimport type {\n CollectionLike,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} 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 /** Extracts all orderBy column values from a raw row (array for multi-column) */\n valueExtractorForRawRow: (row: Record<string, unknown>) => unknown\n /** Extracts only the first column value - used for index-based cursor */\n firstColumnValueExtractor: (row: Record<string, unknown>) => unknown\n /** Index on the first orderBy column - used for lazy loading */\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 setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\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\n return {\n compiledExpression: compileExpression(clauseWithoutAggregates),\n compareOptions: buildCompareOptions(clause, collection),\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 = compiledOrderBy[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 = compiledOrderBy[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 let orderByOptimizationInfo: OrderByOptimizationInfo | undefined\n\n // When there's a limit, we create orderByOptimizationInfo to pass orderBy/limit\n // to loadSubset so the sync layer can optimize the query.\n // We try to use an index on the FIRST orderBy column for lazy loading,\n // even for multi-column orderBy (using wider bounds on first column).\n if (limit) {\n let index: IndexInterface<string | number> | undefined\n let followRefCollection: Collection | undefined\n let firstColumnValueExtractor: CompiledSingleRowExpression | undefined\n let orderByAlias: string = rawQuery.from.alias\n\n // Try to create/find an index on the FIRST orderBy column for lazy loading\n const firstClause = orderByClause[0]!\n const firstOrderByExpression = firstClause.expression\n\n if (firstOrderByExpression.type === `ref`) {\n const followRefResult = followRef(\n rawQuery,\n firstOrderByExpression,\n collection,\n )\n\n if (followRefResult) {\n followRefCollection = followRefResult.collection\n const fieldName = followRefResult.path[0]\n const compareOpts = buildCompareOptions(\n firstClause,\n followRefCollection,\n )\n\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n compareOpts,\n compare,\n )\n }\n\n // First column value extractor - used for index cursor\n firstColumnValueExtractor = compileExpression(\n new PropRef(followRefResult.path),\n true,\n ) as CompiledSingleRowExpression\n\n index = findIndexForField(\n followRefCollection,\n followRefResult.path,\n compareOpts,\n )\n\n // Only use the index if it supports range queries\n if (!index?.supports(`gt`)) {\n index = undefined\n }\n\n orderByAlias =\n firstOrderByExpression.path.length > 1\n ? String(firstOrderByExpression.path[0])\n : rawQuery.from.alias\n }\n }\n\n // Only create comparator and value extractors if the first column is a ref expression\n // For aggregate or computed expressions, we can't extract values from raw collection rows\n if (!firstColumnValueExtractor) {\n // Skip optimization for non-ref expressions (aggregates, computed values, etc.)\n // The query will still work, but without lazy loading optimization\n } else {\n // Build value extractors for all columns (must all be ref expressions for multi-column)\n // Check if all orderBy expressions are ref types (required for multi-column extraction)\n const allColumnsAreRefs = orderByClause.every(\n (clause) => clause.expression.type === `ref`,\n )\n\n // Create extractors for all columns if they're all refs\n const allColumnExtractors:\n | Array<CompiledSingleRowExpression>\n | undefined = allColumnsAreRefs\n ? orderByClause.map((clause) => {\n // We know it's a ref since we checked allColumnsAreRefs\n const refExpr = clause.expression as PropRef\n const followResult = followRef(rawQuery, refExpr, collection)\n if (followResult) {\n return compileExpression(\n new PropRef(followResult.path),\n true,\n ) as CompiledSingleRowExpression\n }\n // Fallback for refs that don't follow\n return compileExpression(\n clause.expression,\n true,\n ) as CompiledSingleRowExpression\n })\n : undefined\n\n // Create a comparator for raw rows (used for tracking sent values)\n // This compares ALL orderBy columns for proper ordering\n const comparator = (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined,\n ) => {\n if (orderByClause.length === 1) {\n // Single column: extract and compare\n const extractedA = a ? firstColumnValueExtractor(a) : a\n const extractedB = b ? firstColumnValueExtractor(b) : b\n return compare(extractedA, extractedB)\n }\n if (allColumnExtractors) {\n // Multi-column with all refs: extract all values and compare\n const extractAll = (\n row: Record<string, unknown> | null | undefined,\n ) => {\n if (!row) return row\n return allColumnExtractors.map((extractor) => extractor(row))\n }\n return compare(extractAll(a), extractAll(b))\n }\n // Fallback: can't compare (shouldn't happen since we skip non-ref cases)\n return 0\n }\n\n // Create a value extractor for raw rows that extracts ALL orderBy column values\n // This is used for tracking sent values and building composite cursors\n const rawRowValueExtractor = (row: Record<string, unknown>): unknown => {\n if (orderByClause.length === 1) {\n // Single column: return single value\n return firstColumnValueExtractor(row)\n }\n if (allColumnExtractors) {\n // Multi-column: return array of all values\n return allColumnExtractors.map((extractor) => extractor(row))\n }\n // Fallback (shouldn't happen)\n return undefined\n }\n\n orderByOptimizationInfo = {\n alias: orderByAlias,\n offset: offset ?? 0,\n limit,\n comparator,\n valueExtractorForRawRow: rawRowValueExtractor,\n firstColumnValueExtractor: firstColumnValueExtractor,\n index,\n orderBy: orderByClause,\n }\n\n // Store the optimization info keyed by collection ID\n // Use the followed collection if available, otherwise use the main collection\n const targetCollectionId = followRefCollection?.id ?? collection.id\n optimizableOrderByCollections[targetCollectionId] =\n orderByOptimizationInfo\n\n // Set up lazy loading callback if we have an index\n if (index) {\n setSizeCallback = (getSize: () => number) => {\n optimizableOrderByCollections[targetCollectionId]![`dataNeeded`] =\n () => {\n const size = getSize()\n return Math.max(0, orderByOptimizationInfo!.limit - size)\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 setWindowFn: (\n windowFn: (options: { offset?: number; limit?: number }) => void,\n ) => {\n setWindowFn(\n // We wrap the move function such that we update the orderByOptimizationInfo\n // because that is used by the `dataNeeded` callback to determine if we need to load more data\n (options) => {\n windowFn(options)\n if (orderByOptimizationInfo) {\n orderByOptimizationInfo.offset =\n options.offset ?? orderByOptimizationInfo.offset\n orderByOptimizationInfo.limit =\n options.limit ?? orderByOptimizationInfo.limit\n }\n },\n )\n },\n }),\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n\n/**\n * Builds a comparison configuration object that uses the values provided in the orderBy clause.\n * If no string sort configuration is provided it defaults to the collection's string sort configuration.\n */\nexport function buildCompareOptions(\n clause: OrderByClause,\n collection: CollectionLike<any, any>,\n): CompareOptions {\n if (clause.compareOptions.stringSort !== undefined) {\n return clause.compareOptions\n }\n\n return {\n ...collection.compareOptions,\n direction: clause.compareOptions.direction,\n nulls: clause.compareOptions.nulls,\n }\n}\n"],"names":["replaceAggregatesByRefs","compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","PropRef","findIndexForField","orderByWithFractionalIndex"],"mappings":"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"}
|
|
@@ -11,8 +11,12 @@ export type OrderByOptimizationInfo = {
|
|
|
11
11
|
offset: number;
|
|
12
12
|
limit: number;
|
|
13
13
|
comparator: (a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined) => number;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/** Extracts all orderBy column values from a raw row (array for multi-column) */
|
|
15
|
+
valueExtractorForRawRow: (row: Record<string, unknown>) => unknown;
|
|
16
|
+
/** Extracts only the first column value - used for index-based cursor */
|
|
17
|
+
firstColumnValueExtractor: (row: Record<string, unknown>) => unknown;
|
|
18
|
+
/** Index on the first orderBy column - used for lazy loading */
|
|
19
|
+
index?: IndexInterface<string | number>;
|
|
16
20
|
dataNeeded?: () => number;
|
|
17
21
|
};
|
|
18
22
|
/**
|
|
@@ -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 { 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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"}
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression-helpers.cjs","sources":["../../../src/query/expression-helpers.ts"],"sourcesContent":["/**\n * Expression Helpers for TanStack DB\n *\n * These utilities help parse LoadSubsetOptions (where, orderBy, limit) from TanStack DB\n * into formats suitable for your API backend. They provide a generic way to traverse\n * expression trees without having to implement your own parser.\n *\n * @example\n * ```typescript\n * import { parseWhereExpression, parseOrderByExpression } from '@tanstack/db'\n *\n * queryFn: async (ctx) => {\n * const { limit, where, orderBy } = ctx.meta?.loadSubsetOptions ?? {}\n *\n * // Convert expression tree to filters\n * const filters = parseWhereExpression(where, {\n * eq: (field, value) => ({ [field]: value }),\n * lt: (field, value) => ({ [`${field}_lt`]: value }),\n * and: (filters) => Object.assign({}, ...filters)\n * })\n *\n * // Extract sort information\n * const sort = parseOrderByExpression(orderBy)\n *\n * return api.getProducts({ ...filters, sort, limit })\n * }\n * ```\n */\n\nimport type { IR, OperatorName } from \"../index.js\"\n\ntype BasicExpression<T = any> = IR.BasicExpression<T>\ntype OrderBy = IR.OrderBy\n\n/**\n * Represents a simple field path extracted from an expression.\n * Can include string keys for object properties and numbers for array indices.\n */\nexport type FieldPath = Array<string | number>\n\n/**\n * Represents a simple comparison operation\n */\nexport interface SimpleComparison {\n field: FieldPath\n operator: string\n value?: any // Optional for operators like isNull and isUndefined that don't have a value\n}\n\n/**\n * Options for customizing how WHERE expressions are parsed\n */\nexport interface ParseWhereOptions<T = any> {\n /**\n * Handler functions for different operators.\n * Each handler receives the parsed field path(s) and value(s) and returns your custom format.\n *\n * Supported operators from TanStack DB:\n * - Comparison: eq, gt, gte, lt, lte, in, like, ilike\n * - Logical: and, or, not\n * - Null checking: isNull, isUndefined\n * - String functions: upper, lower, length, concat\n * - Numeric: add\n * - Utility: coalesce\n * - Aggregates: count, avg, sum, min, max\n */\n handlers: {\n [K in OperatorName]?: (...args: Array<any>) => T\n } & {\n [key: string]: (...args: Array<any>) => T\n }\n /**\n * Optional handler for when an unknown operator is encountered.\n * If not provided, unknown operators throw an error.\n */\n onUnknownOperator?: (operator: string, args: Array<any>) => T\n}\n\n/**\n * Result of parsing an ORDER BY expression\n */\nexport interface ParsedOrderBy {\n field: FieldPath\n direction: `asc` | `desc`\n nulls: `first` | `last`\n /** String sorting method: 'lexical' (default) or 'locale' (locale-aware) */\n stringSort?: `lexical` | `locale`\n /** Locale for locale-aware string sorting (e.g., 'en-US') */\n locale?: string\n /** Additional options for locale-aware sorting */\n localeOptions?: object\n}\n\n/**\n * Extracts the field path from a PropRef expression.\n * Returns null for non-ref expressions.\n *\n * @param expr - The expression to extract from\n * @returns The field path array, or null\n *\n * @example\n * ```typescript\n * const field = extractFieldPath(someExpression)\n * // Returns: ['product', 'category']\n * ```\n */\nexport function extractFieldPath(expr: BasicExpression): FieldPath | null {\n if (expr.type === `ref`) {\n return expr.path\n }\n return null\n}\n\n/**\n * Extracts the value from a Value expression.\n * Returns undefined for non-value expressions.\n *\n * @param expr - The expression to extract from\n * @returns The extracted value\n *\n * @example\n * ```typescript\n * const val = extractValue(someExpression)\n * // Returns: 'electronics'\n * ```\n */\nexport function extractValue(expr: BasicExpression): any {\n if (expr.type === `val`) {\n return expr.value\n }\n return undefined\n}\n\n/**\n * Generic expression tree walker that visits each node in the expression.\n * Useful for implementing custom parsing logic.\n *\n * @param expr - The expression to walk\n * @param visitor - Visitor function called for each node\n *\n * @example\n * ```typescript\n * walkExpression(whereExpr, (node) => {\n * if (node.type === 'func' && node.name === 'eq') {\n * console.log('Found equality comparison')\n * }\n * })\n * ```\n */\nexport function walkExpression(\n expr: BasicExpression | undefined | null,\n visitor: (node: BasicExpression) => void\n): void {\n if (!expr) return\n\n visitor(expr)\n\n if (expr.type === `func`) {\n expr.args.forEach((arg: BasicExpression) => walkExpression(arg, visitor))\n }\n}\n\n/**\n * Parses a WHERE expression into a custom format using provided handlers.\n *\n * This is the main helper for converting TanStack DB where clauses into your API's filter format.\n * You provide handlers for each operator, and this function traverses the expression tree\n * and calls the appropriate handlers.\n *\n * @param expr - The WHERE expression to parse\n * @param options - Configuration with handler functions for each operator\n * @returns The parsed result in your custom format\n *\n * @example\n * ```typescript\n * // REST API with query parameters\n * const filters = parseWhereExpression(where, {\n * handlers: {\n * eq: (field, value) => ({ [field.join('.')]: value }),\n * lt: (field, value) => ({ [`${field.join('.')}_lt`]: value }),\n * gt: (field, value) => ({ [`${field.join('.')}_gt`]: value }),\n * and: (...filters) => Object.assign({}, ...filters),\n * or: (...filters) => ({ $or: filters })\n * }\n * })\n * // Returns: { category: 'electronics', price_lt: 100 }\n * ```\n *\n * @example\n * ```typescript\n * // GraphQL where clause\n * const where = parseWhereExpression(whereExpr, {\n * handlers: {\n * eq: (field, value) => ({ [field.join('_')]: { _eq: value } }),\n * lt: (field, value) => ({ [field.join('_')]: { _lt: value } }),\n * and: (...filters) => ({ _and: filters })\n * }\n * })\n * ```\n */\nexport function parseWhereExpression<T = any>(\n expr: BasicExpression<boolean> | undefined | null,\n options: ParseWhereOptions<T>\n): T | null {\n if (!expr) return null\n\n const { handlers, onUnknownOperator } = options\n\n // Handle value expressions\n if (expr.type === `val`) {\n return expr.value as unknown as T\n }\n\n // Handle property references\n if (expr.type === `ref`) {\n return expr.path as unknown as T\n }\n\n // Handle function expressions\n // After checking val and ref, expr must be func\n const { name, args } = expr\n const handler = handlers[name]\n\n if (!handler) {\n if (onUnknownOperator) {\n return onUnknownOperator(name, args)\n }\n throw new Error(\n `No handler provided for operator: ${name}. Available handlers: ${Object.keys(handlers).join(`, `)}`\n )\n }\n\n // Parse arguments recursively\n const parsedArgs = args.map((arg: BasicExpression) => {\n // For refs, extract the field path\n if (arg.type === `ref`) {\n return arg.path\n }\n // For values, extract the value\n if (arg.type === `val`) {\n return arg.value\n }\n // For nested functions, recurse (after checking ref and val, must be func)\n return parseWhereExpression(arg, options)\n })\n\n return handler(...parsedArgs)\n}\n\n/**\n * Parses an ORDER BY expression into a simple array of sort specifications.\n *\n * @param orderBy - The ORDER BY expression array\n * @returns Array of parsed order by specifications\n *\n * @example\n * ```typescript\n * const sorts = parseOrderByExpression(orderBy)\n * // Returns: [\n * // { field: ['category'], direction: 'asc', nulls: 'last' },\n * // { field: ['price'], direction: 'desc', nulls: 'last' }\n * // ]\n * ```\n */\nexport function parseOrderByExpression(\n orderBy: OrderBy | undefined | null\n): Array<ParsedOrderBy> {\n if (!orderBy || orderBy.length === 0) {\n return []\n }\n\n return orderBy.map((clause: IR.OrderByClause) => {\n const field = extractFieldPath(clause.expression)\n\n if (!field) {\n throw new Error(\n `ORDER BY expression must be a field reference, got: ${clause.expression.type}`\n )\n }\n\n const { direction, nulls } = clause.compareOptions\n const result: ParsedOrderBy = {\n field,\n direction,\n nulls,\n }\n\n // Add string collation options if present (discriminated union)\n if (`stringSort` in clause.compareOptions) {\n result.stringSort = clause.compareOptions.stringSort\n }\n if (`locale` in clause.compareOptions) {\n result.locale = clause.compareOptions.locale\n }\n if (`localeOptions` in clause.compareOptions) {\n result.localeOptions = clause.compareOptions.localeOptions\n }\n\n return result\n })\n}\n\n/**\n * Extracts all simple comparisons from a WHERE expression.\n * This is useful for simple APIs that only support basic filters.\n *\n * Note: This only works for simple AND-ed conditions and NOT-wrapped comparisons.\n * Throws an error if it encounters unsupported operations like OR or complex nested expressions.\n *\n * NOT operators are flattened by prefixing the operator name (e.g., `not(eq(...))` becomes `not_eq`).\n *\n * @param expr - The WHERE expression to parse\n * @returns Array of simple comparisons\n * @throws Error if expression contains OR or other unsupported operations\n *\n * @example\n * ```typescript\n * const comparisons = extractSimpleComparisons(where)\n * // Returns: [\n * // { field: ['category'], operator: 'eq', value: 'electronics' },\n * // { field: ['price'], operator: 'lt', value: 100 },\n * // { field: ['email'], operator: 'isNull' }, // No value for null checks\n * // { field: ['status'], operator: 'not_eq', value: 'archived' }\n * // ]\n * ```\n */\nexport function extractSimpleComparisons(\n expr: BasicExpression<boolean> | undefined | null\n): Array<SimpleComparison> {\n if (!expr) return []\n\n const comparisons: Array<SimpleComparison> = []\n\n function extract(e: BasicExpression): void {\n if (e.type === `func`) {\n // Handle AND - recurse into both sides\n if (e.name === `and`) {\n e.args.forEach((arg: BasicExpression) => extract(arg))\n return\n }\n\n // Handle NOT - recurse into argument and prefix operator with 'not_'\n if (e.name === `not`) {\n const [arg] = e.args\n if (!arg || arg.type !== `func`) {\n throw new Error(\n `extractSimpleComparisons requires a comparison or null check inside 'not' operator.`\n )\n }\n\n // Handle NOT with null/undefined checks\n const nullCheckOps = [`isNull`, `isUndefined`]\n if (nullCheckOps.includes(arg.name)) {\n const [fieldArg] = arg.args\n const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n if (field) {\n comparisons.push({\n field,\n operator: `not_${arg.name}`,\n // No value for null/undefined checks\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires a field reference for '${arg.name}' operator.`\n )\n }\n return\n }\n\n // Handle NOT with comparison operators\n const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (comparisonOps.includes(arg.name)) {\n const [leftArg, rightArg] = arg.args\n const field = leftArg?.type === `ref` ? leftArg.path : null\n const value = rightArg?.type === `val` ? rightArg.value : null\n\n if (field && value !== undefined) {\n comparisons.push({\n field,\n operator: `not_${arg.name}`,\n value,\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for 'not(${arg.name})' operator.`\n )\n }\n return\n }\n\n // NOT can only wrap simple comparisons or null checks\n throw new Error(\n `extractSimpleComparisons does not support 'not(${arg.name})'. NOT can only wrap comparison operators (eq, gt, gte, lt, lte, in) or null checks (isNull, isUndefined).`\n )\n }\n\n // Throw on unsupported operations\n const unsupportedOps = [\n `or`,\n `like`,\n `ilike`,\n `upper`,\n `lower`,\n `length`,\n `concat`,\n `add`,\n `coalesce`,\n `count`,\n `avg`,\n `sum`,\n `min`,\n `max`,\n ]\n if (unsupportedOps.includes(e.name)) {\n throw new Error(\n `extractSimpleComparisons does not support '${e.name}' operator. Use parseWhereExpression with custom handlers for complex expressions.`\n )\n }\n\n // Handle null/undefined check operators (single argument, no value)\n const nullCheckOps = [`isNull`, `isUndefined`]\n if (nullCheckOps.includes(e.name)) {\n const [fieldArg] = e.args\n\n // Extract field (must be a ref)\n const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n if (field) {\n comparisons.push({\n field,\n operator: e.name,\n // No value for null/undefined checks\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires a field reference for '${e.name}' operator.`\n )\n }\n return\n }\n\n // Handle comparison operators\n const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (comparisonOps.includes(e.name)) {\n const [leftArg, rightArg] = e.args\n\n // Extract field and value\n const field = leftArg?.type === `ref` ? leftArg.path : null\n const value = rightArg?.type === `val` ? rightArg.value : null\n\n if (field && value !== undefined) {\n comparisons.push({\n field,\n operator: e.name,\n value,\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for '${e.name}' operator.`\n )\n }\n } else {\n // Unknown operator\n throw new Error(\n `extractSimpleComparisons encountered unknown operator: '${e.name}'`\n )\n }\n }\n }\n\n extract(expr)\n return comparisons\n}\n\n/**\n * Convenience function to get all LoadSubsetOptions in a pre-parsed format.\n * Good starting point for simple use cases.\n *\n * @param options - The LoadSubsetOptions from ctx.meta\n * @returns Pre-parsed filters, sorts, and limit\n *\n * @example\n * ```typescript\n * queryFn: async (ctx) => {\n * const parsed = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)\n *\n * // Convert to your API format\n * return api.getProducts({\n * ...Object.fromEntries(\n * parsed.filters.map(f => [`${f.field.join('.')}_${f.operator}`, f.value])\n * ),\n * sort: parsed.sorts.map(s => `${s.field.join('.')}:${s.direction}`).join(','),\n * limit: parsed.limit\n * })\n * }\n * ```\n */\nexport function parseLoadSubsetOptions(\n options:\n | {\n where?: BasicExpression<boolean>\n orderBy?: OrderBy\n limit?: number\n }\n | undefined\n | null\n): {\n filters: Array<SimpleComparison>\n sorts: Array<ParsedOrderBy>\n limit?: number\n} {\n if (!options) {\n return { filters: [], sorts: [] }\n }\n\n return {\n filters: extractSimpleComparisons(options.where),\n sorts: parseOrderByExpression(options.orderBy),\n limit: options.limit,\n }\n}\n"],"names":["nullCheckOps","comparisonOps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"expression-helpers.cjs","sources":["../../../src/query/expression-helpers.ts"],"sourcesContent":["/**\n * Expression Helpers for TanStack DB\n *\n * These utilities help parse LoadSubsetOptions (where, orderBy, limit) from TanStack DB\n * into formats suitable for your API backend. They provide a generic way to traverse\n * expression trees without having to implement your own parser.\n *\n * @example\n * ```typescript\n * import { parseWhereExpression, parseOrderByExpression } from '@tanstack/db'\n *\n * queryFn: async (ctx) => {\n * const { limit, where, orderBy } = ctx.meta?.loadSubsetOptions ?? {}\n *\n * // Convert expression tree to filters\n * const filters = parseWhereExpression(where, {\n * eq: (field, value) => ({ [field]: value }),\n * lt: (field, value) => ({ [`${field}_lt`]: value }),\n * and: (filters) => Object.assign({}, ...filters)\n * })\n *\n * // Extract sort information\n * const sort = parseOrderByExpression(orderBy)\n *\n * return api.getProducts({ ...filters, sort, limit })\n * }\n * ```\n */\n\nimport type { IR, OperatorName } from '../index.js'\n\ntype BasicExpression<T = any> = IR.BasicExpression<T>\ntype OrderBy = IR.OrderBy\n\n/**\n * Represents a simple field path extracted from an expression.\n * Can include string keys for object properties and numbers for array indices.\n */\nexport type FieldPath = Array<string | number>\n\n/**\n * Represents a simple comparison operation\n */\nexport interface SimpleComparison {\n field: FieldPath\n operator: string\n value?: any // Optional for operators like isNull and isUndefined that don't have a value\n}\n\n/**\n * Options for customizing how WHERE expressions are parsed\n */\nexport interface ParseWhereOptions<T = any> {\n /**\n * Handler functions for different operators.\n * Each handler receives the parsed field path(s) and value(s) and returns your custom format.\n *\n * Supported operators from TanStack DB:\n * - Comparison: eq, gt, gte, lt, lte, in, like, ilike\n * - Logical: and, or, not\n * - Null checking: isNull, isUndefined\n * - String functions: upper, lower, length, concat\n * - Numeric: add\n * - Utility: coalesce\n * - Aggregates: count, avg, sum, min, max\n */\n handlers: {\n [K in OperatorName]?: (...args: Array<any>) => T\n } & {\n [key: string]: (...args: Array<any>) => T\n }\n /**\n * Optional handler for when an unknown operator is encountered.\n * If not provided, unknown operators throw an error.\n */\n onUnknownOperator?: (operator: string, args: Array<any>) => T\n}\n\n/**\n * Result of parsing an ORDER BY expression\n */\nexport interface ParsedOrderBy {\n field: FieldPath\n direction: `asc` | `desc`\n nulls: `first` | `last`\n /** String sorting method: 'lexical' (default) or 'locale' (locale-aware) */\n stringSort?: `lexical` | `locale`\n /** Locale for locale-aware string sorting (e.g., 'en-US') */\n locale?: string\n /** Additional options for locale-aware sorting */\n localeOptions?: object\n}\n\n/**\n * Extracts the field path from a PropRef expression.\n * Returns null for non-ref expressions.\n *\n * @param expr - The expression to extract from\n * @returns The field path array, or null\n *\n * @example\n * ```typescript\n * const field = extractFieldPath(someExpression)\n * // Returns: ['product', 'category']\n * ```\n */\nexport function extractFieldPath(expr: BasicExpression): FieldPath | null {\n if (expr.type === `ref`) {\n return expr.path\n }\n return null\n}\n\n/**\n * Extracts the value from a Value expression.\n * Returns undefined for non-value expressions.\n *\n * @param expr - The expression to extract from\n * @returns The extracted value\n *\n * @example\n * ```typescript\n * const val = extractValue(someExpression)\n * // Returns: 'electronics'\n * ```\n */\nexport function extractValue(expr: BasicExpression): any {\n if (expr.type === `val`) {\n return expr.value\n }\n return undefined\n}\n\n/**\n * Generic expression tree walker that visits each node in the expression.\n * Useful for implementing custom parsing logic.\n *\n * @param expr - The expression to walk\n * @param visitor - Visitor function called for each node\n *\n * @example\n * ```typescript\n * walkExpression(whereExpr, (node) => {\n * if (node.type === 'func' && node.name === 'eq') {\n * console.log('Found equality comparison')\n * }\n * })\n * ```\n */\nexport function walkExpression(\n expr: BasicExpression | undefined | null,\n visitor: (node: BasicExpression) => void,\n): void {\n if (!expr) return\n\n visitor(expr)\n\n if (expr.type === `func`) {\n expr.args.forEach((arg: BasicExpression) => walkExpression(arg, visitor))\n }\n}\n\n/**\n * Parses a WHERE expression into a custom format using provided handlers.\n *\n * This is the main helper for converting TanStack DB where clauses into your API's filter format.\n * You provide handlers for each operator, and this function traverses the expression tree\n * and calls the appropriate handlers.\n *\n * @param expr - The WHERE expression to parse\n * @param options - Configuration with handler functions for each operator\n * @returns The parsed result in your custom format\n *\n * @example\n * ```typescript\n * // REST API with query parameters\n * const filters = parseWhereExpression(where, {\n * handlers: {\n * eq: (field, value) => ({ [field.join('.')]: value }),\n * lt: (field, value) => ({ [`${field.join('.')}_lt`]: value }),\n * gt: (field, value) => ({ [`${field.join('.')}_gt`]: value }),\n * and: (...filters) => Object.assign({}, ...filters),\n * or: (...filters) => ({ $or: filters })\n * }\n * })\n * // Returns: { category: 'electronics', price_lt: 100 }\n * ```\n *\n * @example\n * ```typescript\n * // GraphQL where clause\n * const where = parseWhereExpression(whereExpr, {\n * handlers: {\n * eq: (field, value) => ({ [field.join('_')]: { _eq: value } }),\n * lt: (field, value) => ({ [field.join('_')]: { _lt: value } }),\n * and: (...filters) => ({ _and: filters })\n * }\n * })\n * ```\n */\nexport function parseWhereExpression<T = any>(\n expr: BasicExpression<boolean> | undefined | null,\n options: ParseWhereOptions<T>,\n): T | null {\n if (!expr) return null\n\n const { handlers, onUnknownOperator } = options\n\n // Handle value expressions\n if (expr.type === `val`) {\n return expr.value as unknown as T\n }\n\n // Handle property references\n if (expr.type === `ref`) {\n return expr.path as unknown as T\n }\n\n // Handle function expressions\n // After checking val and ref, expr must be func\n const { name, args } = expr\n const handler = handlers[name]\n\n if (!handler) {\n if (onUnknownOperator) {\n return onUnknownOperator(name, args)\n }\n throw new Error(\n `No handler provided for operator: ${name}. Available handlers: ${Object.keys(handlers).join(`, `)}`,\n )\n }\n\n // Parse arguments recursively\n const parsedArgs = args.map((arg: BasicExpression) => {\n // For refs, extract the field path\n if (arg.type === `ref`) {\n return arg.path\n }\n // For values, extract the value\n if (arg.type === `val`) {\n return arg.value\n }\n // For nested functions, recurse (after checking ref and val, must be func)\n return parseWhereExpression(arg, options)\n })\n\n return handler(...parsedArgs)\n}\n\n/**\n * Parses an ORDER BY expression into a simple array of sort specifications.\n *\n * @param orderBy - The ORDER BY expression array\n * @returns Array of parsed order by specifications\n *\n * @example\n * ```typescript\n * const sorts = parseOrderByExpression(orderBy)\n * // Returns: [\n * // { field: ['category'], direction: 'asc', nulls: 'last' },\n * // { field: ['price'], direction: 'desc', nulls: 'last' }\n * // ]\n * ```\n */\nexport function parseOrderByExpression(\n orderBy: OrderBy | undefined | null,\n): Array<ParsedOrderBy> {\n if (!orderBy || orderBy.length === 0) {\n return []\n }\n\n return orderBy.map((clause: IR.OrderByClause) => {\n const field = extractFieldPath(clause.expression)\n\n if (!field) {\n throw new Error(\n `ORDER BY expression must be a field reference, got: ${clause.expression.type}`,\n )\n }\n\n const { direction, nulls } = clause.compareOptions\n const result: ParsedOrderBy = {\n field,\n direction,\n nulls,\n }\n\n // Add string collation options if present (discriminated union)\n if (`stringSort` in clause.compareOptions) {\n result.stringSort = clause.compareOptions.stringSort\n }\n if (`locale` in clause.compareOptions) {\n result.locale = clause.compareOptions.locale\n }\n if (`localeOptions` in clause.compareOptions) {\n result.localeOptions = clause.compareOptions.localeOptions\n }\n\n return result\n })\n}\n\n/**\n * Extracts all simple comparisons from a WHERE expression.\n * This is useful for simple APIs that only support basic filters.\n *\n * Note: This only works for simple AND-ed conditions and NOT-wrapped comparisons.\n * Throws an error if it encounters unsupported operations like OR or complex nested expressions.\n *\n * NOT operators are flattened by prefixing the operator name (e.g., `not(eq(...))` becomes `not_eq`).\n *\n * @param expr - The WHERE expression to parse\n * @returns Array of simple comparisons\n * @throws Error if expression contains OR or other unsupported operations\n *\n * @example\n * ```typescript\n * const comparisons = extractSimpleComparisons(where)\n * // Returns: [\n * // { field: ['category'], operator: 'eq', value: 'electronics' },\n * // { field: ['price'], operator: 'lt', value: 100 },\n * // { field: ['email'], operator: 'isNull' }, // No value for null checks\n * // { field: ['status'], operator: 'not_eq', value: 'archived' }\n * // ]\n * ```\n */\nexport function extractSimpleComparisons(\n expr: BasicExpression<boolean> | undefined | null,\n): Array<SimpleComparison> {\n if (!expr) return []\n\n const comparisons: Array<SimpleComparison> = []\n\n function extract(e: BasicExpression): void {\n if (e.type === `func`) {\n // Handle AND - recurse into both sides\n if (e.name === `and`) {\n e.args.forEach((arg: BasicExpression) => extract(arg))\n return\n }\n\n // Handle NOT - recurse into argument and prefix operator with 'not_'\n if (e.name === `not`) {\n const [arg] = e.args\n if (!arg || arg.type !== `func`) {\n throw new Error(\n `extractSimpleComparisons requires a comparison or null check inside 'not' operator.`,\n )\n }\n\n // Handle NOT with null/undefined checks\n const nullCheckOps = [`isNull`, `isUndefined`]\n if (nullCheckOps.includes(arg.name)) {\n const [fieldArg] = arg.args\n const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n if (field) {\n comparisons.push({\n field,\n operator: `not_${arg.name}`,\n // No value for null/undefined checks\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires a field reference for '${arg.name}' operator.`,\n )\n }\n return\n }\n\n // Handle NOT with comparison operators\n const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (comparisonOps.includes(arg.name)) {\n const [leftArg, rightArg] = arg.args\n const field = leftArg?.type === `ref` ? leftArg.path : null\n const value = rightArg?.type === `val` ? rightArg.value : null\n\n if (field && value !== undefined) {\n comparisons.push({\n field,\n operator: `not_${arg.name}`,\n value,\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for 'not(${arg.name})' operator.`,\n )\n }\n return\n }\n\n // NOT can only wrap simple comparisons or null checks\n throw new Error(\n `extractSimpleComparisons does not support 'not(${arg.name})'. NOT can only wrap comparison operators (eq, gt, gte, lt, lte, in) or null checks (isNull, isUndefined).`,\n )\n }\n\n // Throw on unsupported operations\n const unsupportedOps = [\n `or`,\n `like`,\n `ilike`,\n `upper`,\n `lower`,\n `length`,\n `concat`,\n `add`,\n `coalesce`,\n `count`,\n `avg`,\n `sum`,\n `min`,\n `max`,\n ]\n if (unsupportedOps.includes(e.name)) {\n throw new Error(\n `extractSimpleComparisons does not support '${e.name}' operator. Use parseWhereExpression with custom handlers for complex expressions.`,\n )\n }\n\n // Handle null/undefined check operators (single argument, no value)\n const nullCheckOps = [`isNull`, `isUndefined`]\n if (nullCheckOps.includes(e.name)) {\n const [fieldArg] = e.args\n\n // Extract field (must be a ref)\n const field = fieldArg?.type === `ref` ? fieldArg.path : null\n\n if (field) {\n comparisons.push({\n field,\n operator: e.name,\n // No value for null/undefined checks\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires a field reference for '${e.name}' operator.`,\n )\n }\n return\n }\n\n // Handle comparison operators\n const comparisonOps = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (comparisonOps.includes(e.name)) {\n const [leftArg, rightArg] = e.args\n\n // Extract field and value\n const field = leftArg?.type === `ref` ? leftArg.path : null\n const value = rightArg?.type === `val` ? rightArg.value : null\n\n if (field && value !== undefined) {\n comparisons.push({\n field,\n operator: e.name,\n value,\n })\n } else {\n throw new Error(\n `extractSimpleComparisons requires simple field-value comparisons. Found complex expression for '${e.name}' operator.`,\n )\n }\n } else {\n // Unknown operator\n throw new Error(\n `extractSimpleComparisons encountered unknown operator: '${e.name}'`,\n )\n }\n }\n }\n\n extract(expr)\n return comparisons\n}\n\n/**\n * Convenience function to get all LoadSubsetOptions in a pre-parsed format.\n * Good starting point for simple use cases.\n *\n * @param options - The LoadSubsetOptions from ctx.meta\n * @returns Pre-parsed filters, sorts, and limit\n *\n * @example\n * ```typescript\n * queryFn: async (ctx) => {\n * const parsed = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)\n *\n * // Convert to your API format\n * return api.getProducts({\n * ...Object.fromEntries(\n * parsed.filters.map(f => [`${f.field.join('.')}_${f.operator}`, f.value])\n * ),\n * sort: parsed.sorts.map(s => `${s.field.join('.')}:${s.direction}`).join(','),\n * limit: parsed.limit\n * })\n * }\n * ```\n */\nexport function parseLoadSubsetOptions(\n options:\n | {\n where?: BasicExpression<boolean>\n orderBy?: OrderBy\n limit?: number\n }\n | undefined\n | null,\n): {\n filters: Array<SimpleComparison>\n sorts: Array<ParsedOrderBy>\n limit?: number\n} {\n if (!options) {\n return { filters: [], sorts: [] }\n }\n\n return {\n filters: extractSimpleComparisons(options.where),\n sorts: parseOrderByExpression(options.orderBy),\n limit: options.limit,\n }\n}\n"],"names":["nullCheckOps","comparisonOps"],"mappings":"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"}
|
|
@@ -5,5 +5,5 @@ export { compileQuery } from './compiler/index.js';
|
|
|
5
5
|
export { createLiveQueryCollection, liveQueryCollectionOptions, } from './live-query-collection.js';
|
|
6
6
|
export { type LiveQueryCollectionConfig } from './live/types.js';
|
|
7
7
|
export { type LiveQueryCollectionUtils } from './live/collection-config-builder.js';
|
|
8
|
-
export { isWhereSubset, unionWherePredicates, minusWherePredicates, isOrderBySubset, isLimitSubset, isPredicateSubset, } from './predicate-utils.js';
|
|
8
|
+
export { isWhereSubset, unionWherePredicates, minusWherePredicates, isOrderBySubset, isLimitSubset, isOffsetLimitSubset, isPredicateSubset, } from './predicate-utils.js';
|
|
9
9
|
export { DeduplicatedLoadSubset } from './subset-dedupe.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from
|
|
1
|
+
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from './builder/types'\nimport type { Collection, CollectionImpl } from '../collection/index.js'\nimport type { NamespacedRow } from '../types'\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n singleResult?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate | Select\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where =\n | BasicExpression<boolean>\n | { expression: BasicExpression<boolean>; residual?: boolean }\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string,\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string,\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string>, // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T, // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>,\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>,\n ) {\n super()\n }\n}\n\n/**\n * Runtime helper to detect IR expression-like objects.\n * Prefer this over ad-hoc local implementations to keep behavior consistent.\n */\nexport function isExpressionLike(value: any): boolean {\n return (\n value instanceof Aggregate ||\n value instanceof Func ||\n value instanceof PropRef ||\n value instanceof Value\n )\n}\n\n/**\n * Helper functions for working with Where clauses\n */\n\n/**\n * Extract the expression from a Where clause\n */\nexport function getWhereExpression(where: Where): BasicExpression<boolean> {\n return typeof where === `object` && `expression` in where\n ? where.expression\n : where\n}\n\n/**\n * Extract the expression from a HAVING clause\n * HAVING clauses can contain aggregates, unlike regular WHERE clauses\n */\nexport function getHavingExpression(\n having: Having,\n): BasicExpression | Aggregate {\n return typeof having === `object` && `expression` in having\n ? having.expression\n : having\n}\n\n/**\n * Check if a Where clause is marked as residual\n */\nexport function isResidualWhere(where: Where): boolean {\n return (\n typeof where === `object` &&\n `expression` in where &&\n where.residual === true\n )\n}\n\n/**\n * Create a residual Where clause from an expression\n */\nexport function createResidualWhere(\n expression: BasicExpression<boolean>,\n): Where {\n return { expression, residual: true }\n}\n\nfunction getRefFromAlias(\n query: QueryIR,\n alias: string,\n): CollectionRef | QueryRef | void {\n if (query.from.alias === alias) {\n return query.from\n }\n\n for (const join of query.join || []) {\n if (join.from.alias === alias) {\n return join.from\n }\n }\n}\n\n/**\n * Follows the given reference in a query\n * until its finds the root field the reference points to.\n * @returns The collection, its alias, and the path to the root field in this collection\n */\nexport function followRef(\n query: QueryIR,\n ref: PropRef<any>,\n collection: Collection,\n): { collection: Collection; path: Array<string> } | void {\n if (ref.path.length === 0) {\n return\n }\n\n if (ref.path.length === 1) {\n // This field should be part of this collection\n const field = ref.path[0]!\n // is it part of the select clause?\n if (query.select) {\n const selectedField = query.select[field]\n if (selectedField && selectedField.type === `ref`) {\n return followRef(query, selectedField, collection)\n }\n }\n\n // Either this field is not part of the select clause\n // and thus it must be part of the collection itself\n // or it is part of the select but is not a reference\n // so we can stop here and don't have to follow it\n return { collection, path: [field] }\n }\n\n if (ref.path.length > 1) {\n // This is a nested field\n const [alias, ...rest] = ref.path\n const aliasRef = getRefFromAlias(query, alias!)\n if (!aliasRef) {\n return\n }\n\n if (aliasRef.type === `queryRef`) {\n return followRef(aliasRef.query, new PropRef(rest), collection)\n } else {\n // This is a reference to a collection\n // we can't follow it further\n // so the field must be on the collection itself\n return { collection: aliasRef.collection, path: rest }\n }\n }\n}\n"],"names":[],"mappings":";;AAiEA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAMO,SAAS,iBAAiB,OAAqB;AACpD,SACE,iBAAiB,aACjB,iBAAiB,QACjB,iBAAiB,WACjB,iBAAiB;AAErB;AASO,SAAS,mBAAmB,OAAwC;AACzE,SAAO,OAAO,UAAU,YAAY,gBAAgB,QAChD,MAAM,aACN;AACN;AAMO,SAAS,oBACd,QAC6B;AAC7B,SAAO,OAAO,WAAW,YAAY,gBAAgB,SACjD,OAAO,aACP;AACN;AAKO,SAAS,gBAAgB,OAAuB;AACrD,SACE,OAAO,UAAU,YACjB,gBAAgB,SAChB,MAAM,aAAa;AAEvB;AAKO,SAAS,oBACd,YACO;AACP,SAAO,EAAE,YAAY,UAAU,KAAA;AACjC;AAEA,SAAS,gBACP,OACA,OACiC;AACjC,MAAI,MAAM,KAAK,UAAU,OAAO;AAC9B,WAAO,MAAM;AAAA,EACf;AAEA,aAAW,QAAQ,MAAM,QAAQ,CAAA,GAAI;AACnC,QAAI,KAAK,KAAK,UAAU,OAAO;AAC7B,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;AAOO,SAAS,UACd,OACA,KACA,YACwD;AACxD,MAAI,IAAI,KAAK,WAAW,GAAG;AACzB;AAAA,EACF;AAEA,MAAI,IAAI,KAAK,WAAW,GAAG;AAEzB,UAAM,QAAQ,IAAI,KAAK,CAAC;AAExB,QAAI,MAAM,QAAQ;AAChB,YAAM,gBAAgB,MAAM,OAAO,KAAK;AACxC,UAAI,iBAAiB,cAAc,SAAS,OAAO;AACjD,eAAO,UAAU,OAAO,eAAe,UAAU;AAAA,MACnD;AAAA,IACF;AAMA,WAAO,EAAE,YAAY,MAAM,CAAC,KAAK,EAAA;AAAA,EACnC;AAEA,MAAI,IAAI,KAAK,SAAS,GAAG;AAEvB,UAAM,CAAC,OAAO,GAAG,IAAI,IAAI,IAAI;AAC7B,UAAM,WAAW,gBAAgB,OAAO,KAAM;AAC9C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI,SAAS,SAAS,YAAY;AAChC,aAAO,UAAU,SAAS,OAAO,IAAI,QAAQ,IAAI,GAAG,UAAU;AAAA,IAChE,OAAO;AAIL,aAAO,EAAE,YAAY,SAAS,YAAY,MAAM,KAAA;AAAA,IAClD;AAAA,EACF;AACF;;;;;;;;;;;;;"}
|