Skip to content

Commit ee5d026

Browse files
authored
key all streams and remove keyBy (#144)
1 parent e4feb0c commit ee5d026

34 files changed

Lines changed: 1159 additions & 2519 deletions

.changeset/orange-laws-watch.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@tanstack/react-db": patch
3+
"@tanstack/vue-db": patch
4+
"@tanstack/db": patch
5+
---
6+
7+
the `keyBy` query operator has been removed, keying withing the query pipeline is now automatic

docs/index.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,6 @@ const Todos = () => {
279279
.where('@completed', '=', false)
280280
.orderBy({'@created_at': 'asc'})
281281
.select('@id', '@text')
282-
.keyBy('@id')
283282
)
284283

285284
return <List items={ todos } />
@@ -302,7 +301,6 @@ const Todos = () => {
302301
})
303302
.where('@lists.active', '=', true)
304303
.select(`@todos.id`, `@todos.title`, `@lists.name`)
305-
.keyBy('@id')
306304
)
307305

308306
return <List items={ todos } />
@@ -534,7 +532,6 @@ const Todos = () => {
534532
})
535533
.where('@l.active', '=', true)
536534
.select('@t.id', '@t.text', '@t.status', '@l.name')
537-
.keyBy('@id')
538535
)
539536
540537
// Handle local writes by sending them to your API.

docs/overview.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ const Todos = () => {
283283
.where('@completed', '=', false)
284284
.orderBy({'@created_at': 'asc'})
285285
.select('@id', '@text')
286-
.keyBy('@id')
287286
)
288287

289288
return <List items={ todos } />
@@ -306,7 +305,6 @@ const Todos = () => {
306305
})
307306
.where('@lists.active', '=', true)
308307
.select(`@todos.id`, `@todos.title`, `@lists.name`)
309-
.keyBy('@id')
310308
)
311309

312310
return <List items={ todos } />
@@ -538,7 +536,6 @@ const Todos = () => {
538536
})
539537
.where('@l.active', '=', true)
540538
.select('@t.id', '@t.text', '@t.status', '@l.name')
541-
.keyBy('@id')
542539
)
543540
544541
// Handle local writes by sending them to your API.

examples/react/todo/src/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,13 @@ export default function App() {
286286
const { data: todos } = useLiveQuery((q) =>
287287
q
288288
.from({ todoCollection: todoCollection as Collection<UpdateTodo> })
289-
.keyBy(`@id`)
290289
.orderBy(`@created_at`)
291290
.select(`@id`, `@created_at`, `@text`, `@completed`)
292291
)
293292

294293
const { data: configData } = useLiveQuery((q) =>
295294
q
296295
.from({ configCollection: configCollection as Collection<UpdateConfig> })
297-
.keyBy(`@id`)
298296
.select(`@id`, `@key`, `@value`)
299297
)
300298

packages/db/src/query/compiled-query.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,23 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
112112
return this.resultCollection
113113
}
114114

115-
private sendChangesToInput(inputKey: string, changes: Array<ChangeMessage>) {
115+
private sendChangesToInput(
116+
inputKey: string,
117+
changes: Array<ChangeMessage>,
118+
getId: (item: ChangeMessage[`value`]) => any
119+
) {
116120
const input = this.inputs[inputKey]!
117121
const multiSetArray: MultiSetArray<unknown> = []
118122
for (const change of changes) {
123+
const key = getId(change.value)
119124
if (change.type === `insert`) {
120-
multiSetArray.push([change.value, 1])
125+
multiSetArray.push([[key, change.value], 1])
121126
} else if (change.type === `update`) {
122-
multiSetArray.push([change.previousValue, -1])
123-
multiSetArray.push([change.value, 1])
127+
multiSetArray.push([[key, change.previousValue], -1])
128+
multiSetArray.push([[key, change.value], 1])
124129
} else {
125130
// change.type === `delete`
126-
multiSetArray.push([change.value, -1])
131+
multiSetArray.push([[key, change.value], -1])
127132
}
128133
}
129134
input.sendData(this.version, new MultiSet(multiSetArray))
@@ -157,7 +162,11 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
157162

158163
batch(() => {
159164
Object.entries(this.inputCollections).forEach(([key, collection]) => {
160-
this.sendChangesToInput(key, collection.currentStateAsChanges())
165+
this.sendChangesToInput(
166+
key,
167+
collection.currentStateAsChanges(),
168+
collection.config.getId
169+
)
161170
})
162171
this.incrementVersion()
163172
this.sendFrontierToAllInputs()
@@ -168,7 +177,11 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
168177
fn: () => {
169178
batch(() => {
170179
Object.entries(this.inputCollections).forEach(([key, collection]) => {
171-
this.sendChangesToInput(key, collection.derivedChanges.state)
180+
this.sendChangesToInput(
181+
key,
182+
collection.derivedChanges.state,
183+
collection.config.getId
184+
)
172185
})
173186
this.incrementVersion()
174187
this.sendFrontierToAllInputs()

packages/db/src/query/evaluators.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { evaluateOperandOnNestedRow } from "./extractors.js"
1+
import { evaluateOperandOnNamespacedRow } from "./extractors.js"
22
import { compareValues, convertLikeToRegex, isValueInArray } from "./utils.js"
33
import type {
44
Comparator,
@@ -7,21 +7,22 @@ import type {
77
LogicalOperator,
88
SimpleCondition,
99
} from "./schema.js"
10+
import type { NamespacedRow } from "../types.js"
1011

1112
/**
1213
* Evaluates a condition against a nested row structure
1314
*/
14-
export function evaluateConditionOnNestedRow(
15-
nestedRow: Record<string, unknown>,
15+
export function evaluateConditionOnNamespacedRow(
16+
namespacedRow: NamespacedRow,
1617
condition: Condition,
1718
mainTableAlias?: string,
1819
joinedTableAlias?: string
1920
): boolean {
2021
// Handle simple conditions with exactly 3 elements
2122
if (condition.length === 3 && !Array.isArray(condition[0])) {
2223
const [left, comparator, right] = condition as SimpleCondition
23-
return evaluateSimpleConditionOnNestedRow(
24-
nestedRow,
24+
return evaluateSimpleConditionOnNamespacedRow(
25+
namespacedRow,
2526
left,
2627
comparator,
2728
right,
@@ -38,8 +39,8 @@ export function evaluateConditionOnNestedRow(
3839
![`and`, `or`].includes(condition[1] as string)
3940
) {
4041
// Start with the first condition (first 3 elements)
41-
let result = evaluateSimpleConditionOnNestedRow(
42-
nestedRow,
42+
let result = evaluateSimpleConditionOnNamespacedRow(
43+
namespacedRow,
4344
condition[0],
4445
condition[1] as Comparator,
4546
condition[2],
@@ -53,8 +54,8 @@ export function evaluateConditionOnNestedRow(
5354

5455
// Make sure we have a complete condition to evaluate
5556
if (i + 3 <= condition.length) {
56-
const nextResult = evaluateSimpleConditionOnNestedRow(
57-
nestedRow,
57+
const nextResult = evaluateSimpleConditionOnNamespacedRow(
58+
namespacedRow,
5859
condition[i + 1],
5960
condition[i + 2] as Comparator,
6061
condition[i + 3],
@@ -78,8 +79,8 @@ export function evaluateConditionOnNestedRow(
7879
// Handle nested composite conditions where the first element is an array
7980
if (condition.length > 0 && Array.isArray(condition[0])) {
8081
// Start with the first condition
81-
let result = evaluateConditionOnNestedRow(
82-
nestedRow,
82+
let result = evaluateConditionOnNamespacedRow(
83+
namespacedRow,
8384
condition[0] as Condition,
8485
mainTableAlias,
8586
joinedTableAlias
@@ -96,8 +97,8 @@ export function evaluateConditionOnNestedRow(
9697
if (operator === `and`) {
9798
result =
9899
result &&
99-
evaluateConditionOnNestedRow(
100-
nestedRow,
100+
evaluateConditionOnNamespacedRow(
101+
namespacedRow,
101102
nextCondition,
102103
mainTableAlias,
103104
joinedTableAlias
@@ -106,8 +107,8 @@ export function evaluateConditionOnNestedRow(
106107
// logicalOp === `or`
107108
result =
108109
result ||
109-
evaluateConditionOnNestedRow(
110-
nestedRow,
110+
evaluateConditionOnNamespacedRow(
111+
namespacedRow,
111112
nextCondition,
112113
mainTableAlias,
113114
joinedTableAlias
@@ -125,23 +126,23 @@ export function evaluateConditionOnNestedRow(
125126
/**
126127
* Evaluates a simple condition against a nested row structure
127128
*/
128-
export function evaluateSimpleConditionOnNestedRow(
129-
nestedRow: Record<string, unknown>,
129+
export function evaluateSimpleConditionOnNamespacedRow(
130+
namespacedRow: Record<string, unknown>,
130131
left: ConditionOperand,
131132
comparator: Comparator,
132133
right: ConditionOperand,
133134
mainTableAlias?: string,
134135
joinedTableAlias?: string
135136
): boolean {
136-
const leftValue = evaluateOperandOnNestedRow(
137-
nestedRow,
137+
const leftValue = evaluateOperandOnNamespacedRow(
138+
namespacedRow,
138139
left,
139140
mainTableAlias,
140141
joinedTableAlias
141142
)
142143

143-
const rightValue = evaluateOperandOnNestedRow(
144-
nestedRow,
144+
const rightValue = evaluateOperandOnNamespacedRow(
145+
namespacedRow,
145146
right,
146147
mainTableAlias,
147148
joinedTableAlias

packages/db/src/query/extractors.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import type { AllowedFunctionName, ConditionOperand } from "./schema.js"
33

44
/**
55
* Extracts a value from a nested row structure
6-
* @param nestedRow The nested row structure
6+
* @param namespacedRow The nested row structure
77
* @param columnRef The column reference (may include table.column format)
88
* @param mainTableAlias The main table alias to check first for columns without table reference
99
* @param joinedTableAlias The joined table alias to check second for columns without table reference
1010
* @returns The extracted value or undefined if not found
1111
*/
12-
export function extractValueFromNestedRow(
13-
nestedRow: Record<string, unknown>,
12+
export function extractValueFromNamespacedRow(
13+
namespacedRow: Record<string, unknown>,
1414
columnRef: string,
1515
mainTableAlias?: string,
1616
joinedTableAlias?: string
@@ -20,7 +20,7 @@ export function extractValueFromNestedRow(
2020
const [tableAlias, colName] = columnRef.split(`.`) as [string, string]
2121

2222
// Get the table data
23-
const tableData = nestedRow[tableAlias] as
23+
const tableData = namespacedRow[tableAlias] as
2424
| Record<string, unknown>
2525
| null
2626
| undefined
@@ -34,16 +34,19 @@ export function extractValueFromNestedRow(
3434
return value
3535
} else {
3636
// If no table is specified, first try to find in the main table if provided
37-
if (mainTableAlias && nestedRow[mainTableAlias]) {
38-
const mainTableData = nestedRow[mainTableAlias] as Record<string, unknown>
37+
if (mainTableAlias && namespacedRow[mainTableAlias]) {
38+
const mainTableData = namespacedRow[mainTableAlias] as Record<
39+
string,
40+
unknown
41+
>
3942
if (typeof mainTableData === `object` && columnRef in mainTableData) {
4043
return mainTableData[columnRef]
4144
}
4245
}
4346

4447
// Then try the joined table if provided
45-
if (joinedTableAlias && nestedRow[joinedTableAlias]) {
46-
const joinedTableData = nestedRow[joinedTableAlias] as Record<
48+
if (joinedTableAlias && namespacedRow[joinedTableAlias]) {
49+
const joinedTableData = namespacedRow[joinedTableAlias] as Record<
4750
string,
4851
unknown
4952
>
@@ -53,7 +56,7 @@ export function extractValueFromNestedRow(
5356
}
5457

5558
// If not found in main or joined table, try to find the column in any table
56-
for (const [_tableAlias, tableData] of Object.entries(nestedRow)) {
59+
for (const [_tableAlias, tableData] of Object.entries(namespacedRow)) {
5760
if (
5861
tableData &&
5962
typeof tableData === `object` &&
@@ -69,17 +72,17 @@ export function extractValueFromNestedRow(
6972
/**
7073
* Evaluates an operand against a nested row structure
7174
*/
72-
export function evaluateOperandOnNestedRow(
73-
nestedRow: Record<string, unknown>,
75+
export function evaluateOperandOnNamespacedRow(
76+
namespacedRow: Record<string, unknown>,
7477
operand: ConditionOperand,
7578
mainTableAlias?: string,
7679
joinedTableAlias?: string
7780
): unknown {
7881
// Handle column references
7982
if (typeof operand === `string` && operand.startsWith(`@`)) {
8083
const columnRef = operand.substring(1)
81-
return extractValueFromNestedRow(
82-
nestedRow,
84+
return extractValueFromNamespacedRow(
85+
namespacedRow,
8386
columnRef,
8487
mainTableAlias,
8588
joinedTableAlias
@@ -92,17 +95,17 @@ export function evaluateOperandOnNestedRow(
9295

9396
if (typeof colRef === `string`) {
9497
// First try to extract from nested row structure
95-
const nestedValue = extractValueFromNestedRow(
96-
nestedRow,
98+
const nestedValue = extractValueFromNamespacedRow(
99+
namespacedRow,
97100
colRef,
98101
mainTableAlias,
99102
joinedTableAlias
100103
)
101104

102105
// If not found in nested structure, check if it's a direct property of the row
103106
// This is important for HAVING clauses that reference aggregated values
104-
if (nestedValue === undefined && colRef in nestedRow) {
105-
return nestedRow[colRef]
107+
if (nestedValue === undefined && colRef in namespacedRow) {
108+
return namespacedRow[colRef]
106109
}
107110

108111
return nestedValue
@@ -121,15 +124,15 @@ export function evaluateOperandOnNestedRow(
121124
// If the arguments are a reference or another expression, evaluate them first
122125
const evaluatedArgs = Array.isArray(args)
123126
? args.map((arg) =>
124-
evaluateOperandOnNestedRow(
125-
nestedRow,
127+
evaluateOperandOnNamespacedRow(
128+
namespacedRow,
126129
arg as ConditionOperand,
127130
mainTableAlias,
128131
joinedTableAlias
129132
)
130133
)
131-
: evaluateOperandOnNestedRow(
132-
nestedRow,
134+
: evaluateOperandOnNamespacedRow(
135+
namespacedRow,
133136
args as ConditionOperand,
134137
mainTableAlias,
135138
joinedTableAlias

0 commit comments

Comments
 (0)