You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Document includes subqueries in live queries guide
Add an Includes section to docs/guides/live-queries.md covering:
- Basic includes with correlation conditions
- Additional filters including parent-referencing WHERE clauses
- Ordering and limiting per parent
- toArray() for plain array results
- Aggregates per parent
- Nested includes
Also add packages/db/INCLUDES.md with architectural documentation
and update the V2 roadmap to reflect implemented features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Improve includes docs: use concrete examples instead of generic "parent/child" terminology
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Document how to use includes with React via subcomponents with useLiveQuery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add React test for includes: child collection subscription via useLiveQuery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/guides/live-queries.md
+220-3Lines changed: 220 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,6 +38,7 @@ The result types are automatically inferred from your query structure, providing
38
38
-[Select Projections](#select)
39
39
-[Joins](#joins)
40
40
-[Subqueries](#subqueries)
41
+
-[Includes](#includes)
41
42
-[groupBy and Aggregations](#groupby-and-aggregations)
42
43
-[findOne](#findone)
43
44
-[Distinct](#distinct)
@@ -747,9 +748,8 @@ A `join` without a `select` will return row objects that are namespaced with the
747
748
748
749
The result type of a join will take into account the join type, with the optionality of the joined fields being determined by the join type.
749
750
750
-
> [!NOTE]
751
-
> We are working on an `include` system that will enable joins that project to a hierarchical object. For example an `issue` row could have a `comments` property that is an array of `comment` rows.
752
-
> See [this issue](https://github.com/TanStack/db/issues/288) for more details.
751
+
> [!TIP]
752
+
> If you need hierarchical results instead of flat joined rows (e.g., each project with its nested issues), see [Includes](#includes) below.
Includes let you nest subqueries inside `.select()` to produce hierarchical results. Instead of joins that flatten 1:N relationships into repeated rows, each parent row gets a nested collection of its related items.
q.from({ p: projectsCollection }).select(({ p }) => ({
1052
+
id: p.id,
1053
+
name: p.name,
1054
+
issues: q
1055
+
.from({ i: issuesCollection })
1056
+
.where(({ i }) =>eq(i.projectId, p.id))
1057
+
.select(({ i }) => ({
1058
+
id: i.id,
1059
+
title: i.title,
1060
+
})),
1061
+
})),
1062
+
)
1063
+
```
1064
+
1065
+
Each project's `issues` field is a live `Collection` that updates incrementally as the underlying data changes.
1066
+
1067
+
### Correlation Condition
1068
+
1069
+
The child query's `.where()` must contain an `eq()` that links a child field to a parent field — this is the **correlation condition**. It tells the system how children relate to parents.
1070
+
1071
+
```ts
1072
+
// The correlation condition: links issues to their parent project
1073
+
.where(({ i }) =>eq(i.projectId, p.id))
1074
+
```
1075
+
1076
+
The correlation condition can appear as a standalone `.where()`, or inside an `and()`:
1077
+
1078
+
```ts
1079
+
// Also valid — correlation is extracted from inside and()
1080
+
.where(({ i }) =>and(eq(i.projectId, p.id), eq(i.status, 'open')))
1081
+
```
1082
+
1083
+
The correlation field does not need to be included in the parent's `.select()`.
1084
+
1085
+
### Additional Filters
1086
+
1087
+
Child queries support additional `.where()` clauses beyond the correlation condition, including filters that reference parent fields:
1088
+
1089
+
```ts
1090
+
q.from({ p: projectsCollection }).select(({ p }) => ({
1091
+
id: p.id,
1092
+
name: p.name,
1093
+
issues: q
1094
+
.from({ i: issuesCollection })
1095
+
.where(({ i }) =>eq(i.projectId, p.id)) // correlation
1096
+
.where(({ i }) =>eq(i.createdBy, p.createdBy)) // parent-referencing filter
1097
+
.where(({ i }) =>eq(i.status, 'open')) // pure child filter
1098
+
.select(({ i }) => ({
1099
+
id: i.id,
1100
+
title: i.title,
1101
+
})),
1102
+
}))
1103
+
```
1104
+
1105
+
Parent-referencing filters are fully reactive — if a parent's field changes, the child results update automatically.
1106
+
1107
+
### Ordering and Limiting
1108
+
1109
+
Child queries support `.orderBy()` and `.limit()`, applied per parent:
1110
+
1111
+
```ts
1112
+
q.from({ p: projectsCollection }).select(({ p }) => ({
1113
+
id: p.id,
1114
+
name: p.name,
1115
+
issues: q
1116
+
.from({ i: issuesCollection })
1117
+
.where(({ i }) =>eq(i.projectId, p.id))
1118
+
.orderBy(({ i }) =>i.createdAt, 'desc')
1119
+
.limit(5)
1120
+
.select(({ i }) => ({
1121
+
id: i.id,
1122
+
title: i.title,
1123
+
})),
1124
+
}))
1125
+
```
1126
+
1127
+
Each project gets its own top-5 issues, not 5 issues shared across all projects.
1128
+
1129
+
### toArray
1130
+
1131
+
By default, each child result is a live `Collection`. If you want a plain array instead, wrap the child query with `toArray()`:
q.from({ p: projectsCollection }).select(({ p }) => ({
1164
+
id: p.id,
1165
+
name: p.name,
1166
+
issueCount: q
1167
+
.from({ i: issuesCollection })
1168
+
.where(({ i }) =>eq(i.projectId, p.id))
1169
+
.select(({ i }) => ({ total: count(i.id) })),
1170
+
})),
1171
+
)
1172
+
```
1173
+
1174
+
Each project gets its own count. The count updates reactively as issues are added or removed.
1175
+
1176
+
### Nested Includes
1177
+
1178
+
Includes nest arbitrarily. For example, projects can include issues, which include comments:
1179
+
1180
+
```ts
1181
+
const tree =createLiveQueryCollection((q) =>
1182
+
q.from({ p: projectsCollection }).select(({ p }) => ({
1183
+
id: p.id,
1184
+
name: p.name,
1185
+
issues: q
1186
+
.from({ i: issuesCollection })
1187
+
.where(({ i }) =>eq(i.projectId, p.id))
1188
+
.select(({ i }) => ({
1189
+
id: i.id,
1190
+
title: i.title,
1191
+
comments: q
1192
+
.from({ c: commentsCollection })
1193
+
.where(({ c }) =>eq(c.issueId, i.id))
1194
+
.select(({ c }) => ({
1195
+
id: c.id,
1196
+
body: c.body,
1197
+
})),
1198
+
})),
1199
+
})),
1200
+
)
1201
+
```
1202
+
1203
+
Each level updates independently and incrementally — adding a comment to an issue does not re-process other issues or projects.
1204
+
1205
+
### Using Includes with React
1206
+
1207
+
When using includes with React, each child `Collection` needs its own `useLiveQuery` subscription to receive reactive updates. Pass the child collection to a subcomponent that calls `useLiveQuery(childCollection)`:
1208
+
1209
+
```tsx
1210
+
import { useLiveQuery } from'@tanstack/react-db'
1211
+
import { eq } from'@tanstack/db'
1212
+
1213
+
function ProjectList() {
1214
+
const { data: projects } =useLiveQuery((q) =>
1215
+
q.from({ p: projectsCollection }).select(({ p }) => ({
1216
+
id: p.id,
1217
+
name: p.name,
1218
+
issues: q
1219
+
.from({ i: issuesCollection })
1220
+
.where(({ i }) =>eq(i.projectId, p.id))
1221
+
.select(({ i }) => ({
1222
+
id: i.id,
1223
+
title: i.title,
1224
+
})),
1225
+
})),
1226
+
)
1227
+
1228
+
return (
1229
+
<ul>
1230
+
{projects.map((project) => (
1231
+
<likey={project.id}>
1232
+
{project.name}
1233
+
{/* Pass the child collection to a subcomponent */}
1234
+
<IssueListissuesCollection={project.issues} />
1235
+
</li>
1236
+
))}
1237
+
</ul>
1238
+
)
1239
+
}
1240
+
1241
+
function IssueList({ issuesCollection }) {
1242
+
// Subscribe to the child collection for reactive updates
Each `IssueList` component independently subscribes to its project's issues. When an issue is added or removed, only the affected `IssueList` re-renders — the parent `ProjectList` does not.
1256
+
1257
+
> [!NOTE]
1258
+
> You must pass the child collection to a subcomponent and subscribe with `useLiveQuery`. Reading `project.issues` directly in the parent without subscribing will give you the collection object, but the component won't re-render when the child data changes.
1259
+
1043
1260
## groupBy and Aggregations
1044
1261
1045
1262
Use `groupBy` to group your data and apply aggregate functions. When you use aggregates in `select` without `groupBy`, the entire result set is treated as a single group.
0 commit comments