Repository navigation
Conversation
… in-place mutations useLiveQuery's data array missed updates whenever the underlying collection mutated a row in place. flushIncludesState (packages/db/src/query/live/collection-config-builder.ts) writes toArray include values onto the parent row without changing its identity, so when syncDataFromCollection called reconcile(Array.from(currentCollection.values())) Solid's "next === target" short-circuit kicked in at the parent level and the include field never reached subscribers. Shallow-clone each row, and clone any array-valued field on it, before handing the snapshot to reconcile. Field-level diffing then actually runs and toArray includes propagate. The state ReactiveMap path is unchanged because subscribeChanges already gets fresh row references from change events; only the array-sync path was affected. Regression test: useLiveQuery with a toArray include over a child collection, then insert a new child and assert the parent's include array grows. Fixes part of TanStack#1571 — the empty-initial-include null-proxy case in collection-config-builder.ts:1851-1867 is a separate concern and will be filed as its own issue.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
ChangestoArray include reactivity fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/solid-db/tests/useLiveQuery.test.tsx (1)
2480-2546: ⚡ Quick winAdd an empty-include regression variant for
#1571.This test validates the non-empty path, but it should also cover a parent that starts with
issueTitles = []and then receives its first child insert. That edge case is the most likely to regress for include reactivity.💡 Suggested test extension
+ it(`should update when a toArray include starts empty and gets first child`, async () => { + const projects = createCollection( + mockSyncCollectionOptions<IncludeProject>({ + id: `toarray-include-projects-empty`, + getKey: (p) => p.id, + initialData: [{ id: 3, name: `Gamma` }], + }), + ) + const issues = createCollection( + mockSyncCollectionOptions<IncludeIssue>({ + id: `toarray-include-issues-empty`, + getKey: (i) => i.id, + initialData: [], + }), + ) + + const rendered = renderHook(() => + useLiveQuery((q) => + q.from({ p: projects }).select(({ p }) => ({ + id: p.id, + issueTitles: toArray( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => i.title), + ), + })), + ), + ) + + await waitFor(() => { + const gamma = rendered.result().find((r: any) => r.id === 3) + expect(gamma.issueTitles).toEqual([]) + }) + + issues.utils.begin() + issues.utils.write({ + type: `insert`, + value: { id: 30, projectId: 3, title: `First Gamma issue` }, + }) + issues.utils.commit() + + await waitFor(() => { + const gamma = rendered.result().find((r: any) => r.id === 3) + expect(gamma.issueTitles).toEqual([`First Gamma issue`]) + }) + })As per coding guidelines, “Test corner cases including: empty arrays/sets, single-element collections, undefined vs null values, resolved promises, async race conditions, and limit/offset edge cases.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/solid-db/tests/useLiveQuery.test.tsx` around lines 2480 - 2546, Add a regression test variant to cover the edge case where a parent row has an empty issueTitles array and then receives its first child insert, which is the most likely scenario to regress for include reactivity. Create a new test case or extend the existing one to verify that when a child issue is inserted for a project that currently has no issues (empty issueTitles array), the parent row's toArray include field properly reflects the new child item. This ensures the clone-on-sync fix handles the transition from an empty array to a populated array correctly.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/solid-db/tests/useLiveQuery.test.tsx`:
- Around line 2480-2546: Add a regression test variant to cover the edge case
where a parent row has an empty issueTitles array and then receives its first
child insert, which is the most likely scenario to regress for include
reactivity. Create a new test case or extend the existing one to verify that
when a child issue is inserted for a project that currently has no issues (empty
issueTitles array), the parent row's toArray include field properly reflects the
new child item. This ensures the clone-on-sync fix handles the transition from
an empty array to a populated array correctly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c172331-41b7-431f-951a-373b5b3b3bf4
📒 Files selected for processing (3)
.changeset/solid-db-reconcile-clone-rows.mdpackages/solid-db/src/useLiveQuery.tspackages/solid-db/tests/useLiveQuery.test.tsx
Per CodeRabbit, exercise the empty -> first-child transition for a toArray include. Parent starts with `issueTitles: []`, a child insert lands, the parent's array must update. The clone-on-sync path has to widen the existing empty array into a new identity so reconcile sees the change. Confirms the same code path handles both the populated-then-grow case and the empty-then-first-child case.
|
Thanks @coderabbitai — added the empty-include variant in 95d2d407. Parent starts with |
|
Tip For best results, initiate chat on the files or code changes.
🐇 ✨ If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
|
I think this change maybe should wait until this PR was merged? #1598 I'm not super deep in solidjs or tanstack-db, but my understanding of reconcile is as follows:
Problem: this change would intentionally remove all pointer equality. So if the row doesn't happen to have an 'id' field, we lose all incrementalization and solid would fully tear down+recreate For blocks.
https://docs.solidjs.com/reference/store-utilities/reconcile Either way, thank you for your work on this! |
|
Thanks for the careful read @Tarmean — you're right that the clone gives up row-level pointer equality, and with the current default reconcile key ( The reason I went with the clone is that #1598's |
|
Thanks @Tarmean — your reading of reconcile's matching is right, and it's worth being precise about whether #1604 stands on its own, gates on #1598, or should be rethought. The question this turns on: does #1598 ( I don't think it does, and that's the case for keeping both. The reproduction in #1571 is:
#1598 fixes the matching step (which row is "old R" and which is "new R" when references diverge), which matters for the cases #1524 surfaces. But the in-place-mutation case never reaches matching: reconcile bails at the recursive same-reference check first. So That's why #1604 only forces a fresh shallow copy of each row and any array-valued field. After that, reconcile's normal logic — pointer equality on still-deep-equal scalars, key match on the rest — runs as advertised. You're right that breaking row-level reference equality has a real cost. Two readings:
A few paths:
My lean is (1), with (3) as a follow-up if field-level diff turns out to be hot. Happy to convert this PR to draft until #1598 merges, then rebase and confirm the test still demonstrates the fix. One other note: the second sub-issue in #1571 (empty initial include → null proxy, never reactive) isn't addressed by either PR. That looks like it needs the |
|
Closing — this has been open without activity long enough that it's more useful to withdraw and let a fresh PR reflect current |
## Summary
Updates the TanStack data + devtools packages in `frontend` to their
latest versions:
| Package | From | To | Kind |
| --- | --- | --- | --- |
| `@tanstack/solid-db` | 0.2.22 | **0.3.12** | dep |
| `@tanstack/query-db-collection` | 1.0.40 | **1.4.8** | dep |
| `@tanstack/solid-query` | 5.101.0 | **5.104.5** | dep |
| `@tanstack/solid-devtools` | 0.8.5 | **0.8.13** | devDep |
| `@tanstack/solid-query-devtools` | 5.101.0 | **5.104.5** | devDep |
```
frontend/src/ts/
├── collections/ # 8 query-backed collections: writes → await/void, isPersisted → when("settled"), id workaround removed
│ ├── results.ts # + last-10 average live-query: empty single-result state ([] → undefined)
│ ├── tags.ts presets.ts connections.ts inbox.ts
│ ├── ape-keys.ts custom-themes.ts result-filter-presets.ts
│ └── utils/misc.ts # applyIdWorkaround deleted (TanStack/db#1524 fixed upstream)
├── ape/server-configuration.ts # fetchQuery → queryClient.query()
├── router/routes.ts # 9× prefetchQuery → query().catch(noop), ensureQueryData → query() + staleTime "static"
└── components/pages/
├── leaderboard/LeaderboardPage.tsx # next-page prefetchQuery → query().catch(noop)
└── profile/ProfileSearchPage.tsx # fetchQuery → queryClient.query()
```
### Breaking changes found and how they were handled
<details>
<summary><b>1. <code>@tanstack/db</code> 0.15.0 — <code>setWindow</code>
on <code>findOne</code> queries (no code change, verified
unused)</b></summary>
Breaking: a `findOne` keeps its compiled limit (1). An ordered
`findOne`'s `setWindow` can move the offset but now **throws
`SetWindowFindOneLimitError`** for any other limit (before it silently
made the `findOne` return N rows); an unordered `findOne` with a nonzero
offset **throws `SetWindowRequiresOrderByError`**.
- Docs: [TanStack DB — Query
windowing](https://tanstack.com/db/latest/docs/queries/windowing)
- Changelog: [`@tanstack/db`
0.15.0](https://github.com/TanStack/db/blob/main/packages/db/CHANGELOG.md)
Our three `findOne()` usages (`results.ts` averages/daily-best) are
aggregate projections and never call `setWindow`/`getWindow` —
**verified unused, no fix needed**.
</details>
<details>
<summary><b>2. <code>@tanstack/db</code> 0.12.0 — removal of
<code>begin({ immediate })</code> (no code change, verified
unused)</b></summary>
Breaking: the `immediate` option of the sync `begin()` API was removed;
writes made while a mutation persists now queue behind it instead of
applying at once.
- Docs: [TanStack DB —
Transactions](https://tanstack.com/db/latest/docs/collections/transactions)
- Changelog: [`@tanstack/db`
0.12.0](https://github.com/TanStack/db/blob/main/packages/db/CHANGELOG.md)
The app never calls `begin({ immediate })` — **verified unused, no fix
needed**.
</details>
<details open>
<summary><b>3. <code>@tanstack/query-db-collection</code> 1.4.0 —
collection writes now reject promises instead of throwing synchronously
(code change)</b></summary>
Breaking: validation errors from `writeInsert`, `writeUpdate`,
`writeDelete`, `writeUpsert` and `writeBatch` now **reject the returned
promise**; before they threw synchronously. The changelog's migration
guidance: *"Await the promise and catch the error."*
- Docs: [TanStack DB — Query collections: direct
writes](https://tanstack.com/db/latest/docs/collections/query-collection)
- Changelog: [`@tanstack/query-db-collection` 1.4.0, PR
#2044](TanStack/db#2044)
Until 1.3.2 these writes returned a sync receipt, so all 31 call sites
in the 8 collections were bare expressions. Since writes now return
`Promise<void>`, they had to be handled or the repo's
`no-floating-promises` lint rule (error severity) fails:
- **24 calls inside async `mutationFn`s → `await`** (e.g.
`collections/tags.ts`, `results.ts`, `presets.ts`, `connections.ts`,
`ape-keys.ts`, `custom-themes.ts`, `result-filter-presets.ts`). This
preserves the old semantics exactly: a write validation failure rejects
the mutation (previously the sync throw already did, inside the async
function) instead of becoming an unhandled rejection.
- **7 calls inside sync `onMutate` / `writeBatch` callbacks → `void`**
(tags `toggleTagActive`/`setTagActive`/`clearActiveTags`, `inbox.ts`
batch delete/read) — sync callbacks can't await; these are
fire-and-forget, as before.
</details>
<details open>
<summary><b>4. <code>@tanstack/db</code> 0.15.x /
<code>query-db-collection</code> 1.3.5 — transaction settlement rework,
<code>isPersisted</code> deprecated (code change)</b></summary>
The sync transaction API gained two moments (accepted vs. visible):
`commit()` receipts and subset loads resolve at *visibility*, while
handler-facing writes resolve at *acceptance*. As part of this,
**`Transaction.isPersisted` is deprecated — "Use `when('settled')`
instead. This alias will be removed in the 1.0 RC."** (`when('settled')`
has the same resolve/reject semantics: resolves with the transaction on
success, rejects with the original error on failure.)
- Docs: [TanStack DB — Transactions:
`when()`](https://tanstack.com/db/latest/docs/collections/transactions)
- Changelog: [`@tanstack/db` 0.12.0 / 1.3.5
entry](https://github.com/TanStack/db/blob/main/packages/query-db-collection/CHANGELOG.md)
All **26** `await transaction.isPersisted.promise` occurrences (public
action wrappers in the 8 collections, e.g. `addCustomTheme`,
`insertApeKey`, `acceptConnection`, `updateResultTags`) → `await
transaction.when("settled")`.
</details>
<details open>
<summary><b>5. <code>@tanstack/solid-db</code> 0.2.46 — live-query
result types include the empty single-result representation (code
change)</b></summary>
Type-level breaking: conditional live-query result types were aligned
with the disabled/empty reactive bindings, so a `findOne()` inside
`useLiveQuery` now types its empty state as `[]` (empty array) in the
union: `{ ...; wpm: number; acc: number } | []`. Property access on the
union (`last10()?.wpm` in `AverageNotice.tsx`) no longer type-checks
(TS2339).
- Docs: [TanStack DB — Solid: live
queries](https://tanstack.com/db/latest/docs/frameworks/solid)
- Changelog: [`@tanstack/solid-db`
0.2.46](https://github.com/TanStack/db/blob/main/packages/solid-db/CHANGELOG.md)
`useUserAverage10LiveQuery` (`results.ts`) now normalizes through a
`createMemo`: `[]` (and `undefined`) → `undefined`, keeping the existing
"no average" rendering path. This is the only consumer of a
single-result live query.
</details>
<details open>
<summary><b>6. <code>@tanstack/query-core</code> 5.102.0 — imperative
<code>QueryClient</code> methods deprecated (code change)</b></summary>
Minor in 5.x (non-breaking: methods still work, removal is "next major
version"), but the repo lints deprecations as errors, so these had to be
migrated to the new API:
- `prefetchQuery` → **`queryClient.query(options)`** — new
`query()`/`infiniteQuery()` methods added in
[#10658](TanStack/query#10658). Unlike
`prefetchQuery` (which never rejected), `query()` rejects on error, so
fire-and-forget prefetches in `router/routes.ts` (9) and
`LeaderboardPage.tsx` (1) now end in `.catch(noop)` to keep the old
swallow-errors behavior.
- `fetchQuery` → **`queryClient.query(options)`** (1:1, both reject on
error): `ape/server-configuration.ts` sync, `ProfileSearchPage.tsx`
username validation.
- `ensureQueryData` → **`queryClient.query({ ...options, staleTime:
"static" })`** — the mapping recommended in the deprecation tag;
`staleTime: "static"` ("never stale") is new in 5.102. Used in the
friends route loader.
- Docs: [TanStack Query —
`QueryClient`](https://tanstack.com/query/latest/docs/framework/solid/api/queryclient#queryclientquery)
</details>
<details>
<summary><b>7. <code>@tanstack/solid-devtools</code> 0.8.5 → 0.8.13 /
core <code>@tanstack/devtools</code> 0.12.2 → 0.15.0 (no code
change)</b></summary>
The adapter is patch-only (0.8.x); the movement is in the core
`@tanstack/devtools` package it wraps. Relevant entries, checked against
our usage in `components/dev/TanstackDevtools.tsx` (`<TsDevTools
plugins={[inline query plugin, hotkeysDevtoolsPlugin(),
SignalsDevtoolsPlugin()]} config={{ defaultOpen: false }} />`):
- **Plugin pane workspace**
([#492](TanStack/devtools#492)): panes can now
be split/stacked/resized, up to 18 open (was 3). User-visible change;
also, `state.activePlugins` in the devtools `localStorage` state is
superseded by `state.layout` — **existing state is migrated
automatically on first read** (unusable entries repaired, unknown ids
dropped), so no app-side action needed.
- **Plugin lifecycle guarantees** (same PR): a pane's mount node is
never removed while the plugin is open (iframes/canvas keep living) and
`destroy` fires exactly once on close — a fix for plugin authors, no API
change.
- **`use*` → `create*` rename**
([#477](TanStack/devtools#477)): the only
breaking tag in the range is `@tanstack/devtools-ui`'s exported
`useTheme` → `createTheme` (prevents React Compiler from transforming
the Solid panel code). We don't import `@tanstack/devtools-ui` directly
— **no impact**.
- Docs: [TanStack Devtools](https://tanstack.com/devtools)
Our plugin objects (`{ id, name, render, defaultOpen }`) satisfy the
unchanged plugin interface — typecheck clean.
`@tanstack/solid-hotkeys-devtools@0.7.0` (unchanged) peers only on
`solid-js`, so no coupling with the new core.
</details>
<details>
<summary><b>8. <code>@tanstack/solid-query-devtools</code> 5.101.0 →
5.104.5 (no code change)</b></summary>
Patch-only range — every release is a version alignment with
`@tanstack/query-devtools`/`@tanstack/solid-query` (5.x stable, no API
changes). `SolidQueryDevtoolsPanel`'s `client` prop is unchanged. Docs:
[TanStack Query
devtools](https://tanstack.com/query/latest/docs/framework/solid/overview)
</details>
<details open>
<summary><b>9. <code>applyIdWorkaround</code> workaround removed (code
change)</b></summary>
`collections/utils/misc.ts` carried `applyIdWorkaround` — a temp
workaround for
[TanStack/db#1524](TanStack/db#1524) (Solid
rendering showed one item for all rows in a `getKey: (it) => it._id`
collection with ordered live queries, fixed by copying `_id` onto an
`id` property of every fetched row). That issue is closed and its fixes
— [solid-db#1598](TanStack/db#1598) (reconcile
key), [solid-db#1604](TanStack/db#1604) (row
cloning in `syncDataFromCollection`),
[solid-db#1825](TanStack/db#1825) (preserve live
result identity for custom-key rows, shipped in `@tanstack/solid-db`
0.2.46, see [Solid live-query
docs](https://tanstack.com/db/latest/docs/frameworks/solid)) — are all
part of the updated 0.3.12.
Removed the helper and all 7 usages (the `queryFn` row-mapping in the
`custom-themes`, `connections`, `ape-keys`, `presets`, `results`,
`result-filter-presets` and `tags` collections). All consumers type rows
via the zod schemas, which never declared `id`, so no code could depend
on the injected property — typecheck confirms.
</details>
### Entries checked and deliberately not actioned
- **`query-db-collection` 1.2.0**: QueryClient-default `placeholderData`
no longer materializes as collection rows, QueryClient-default
`initialData` no longer seeds on-demand subset observers. Our
`QueryClient` (`queries/index.ts`) sets neither globally — no impact.
- **`query-db-collection` 1.3.0**: mutation-handler auto-refetch
deprecated (`warnOnce` when a handler doesn't return `{ refetch: false
}`). Our collections register **no** `onInsert`/`onUpdate`/`onDelete`
handlers (mutations run through `createOptimisticAction` + explicit
`write*`), so the warning never fires. When handlers are ever added, the
library wants an explicit `{ refetch: false }` or manual `await
collection.utils.refetch()`.
- **`@tanstack/solid-db` 0.3.0 (minor)**: opt-in network-first initial
rendering for *persisted* (SQLite) collections (`initialRender: {
strategy: "network-first" }`, new `persistedStatus`/`isPersistedReady`
flags). We use pure Query-backed collections with no persistence adapter
— no impact.
- **`@tanstack/solid-query` 5.104.4**: type-only re-exports switched to
`export type *` — our named imports (incl. `UseQueryResult`) unaffected,
typecheck clean. (Same `export type *` change landed in
`solid-query-devtools`'s `query-devtools` dep — devtools-only, no app
code touched.)
## Evidence
- **Before:** draft — signed-in browser smoke test still owed, no
runtime evidence for the write/settlement rework.
**After:** smoke run on the dev server (3000/5005) as a signed-in test
account:
- **Average notice** (`useUserAverage10LiveQuery`): with ≥10 results it
shows the real average (84 wpm 87% acc); switching the test language so
the settings filter matches zero results, the empty single-result state
(`[]`) renders "no average" via the new `createMemo` normalization, and
switches back when the language returns — the only consumer of a
single-result live query.
- **Tags, presets, custom themes, ape keys**: create → rename → delete
through the real UI for each. Every mutation settled with no error
notification and no optimistic rollback; every `_id`-keyed list rendered
each row with its own data. Renaming a custom theme re-sorted it to the
top with both rows keeping their own name/colors — the
one-item-for-all-rows regression behind the removed `applyIdWorkaround`
(TanStack/db#1524) is not present.
- **Inbox/connections**: friends page renders with the (empty)
connections list; the account has no pending requests, so the
`writeBatch` read/delete path went unexercised.
- **`queryClient.query()`**: profile search rejects an unknown username
("Unknown user", submit disabled) and accepts an existing one (submit
enabled); the about page rendered all four prefetched sections; the
leaderboard rendered its table.
- **Devtools panel**: skipped — core package is patch-only alignment, no
app code touched.
Observed while smoke-testing (filed as bugs, not blockers): rename
modals (custom theme, ape key) apply the change but the modal takes
several seconds to close — `when("settled")` settles later than
`isPersisted` did for query-backed collections; and a SimpleModal whose
boolean input is never touched reports invalid until the checkbox is
toggled once (form layer, untouched by this PR).
- **Automated:**
- `pnpm oxlint --type-aware --type-check` — exit 0, 0 errors
- `pnpm vitest run` — 84 files, **1622/1622 tests pass**
- `pnpm vite build` — success (catches bundler-level issues from the
`export type *` change and the new `db-ivm` dep)
> The original draft note — a signed-in browser smoke test is still
owed: tag/preset/custom-theme create-edit-delete, inbox read/delete, the
average notice on the test page (the transaction-acceptance and
write-validation rework in 1.3.x/1.4.x is runtime behavior the test
suite doesn't exercise), reordering a `_id`-keyed list — e.g. rename a
custom theme so it sorts to the top and check every row shows its own
data (the regression behind the removed #1524 workaround) — and a look
at the redesigned devtools plugins panel (pane workspace layout, plus
the `localStorage` state migration for anyone who has customized open
panes) — is now resolved by the smoke run above; the observed issues
remain as filed bugs, not blockers.
## Merge Danger
**Door:** two-way
Dependency bump plus mechanical API migration; reverting the PR restores
the prior versions and behavior. No schema, storage, or API contract
changes — a rollback costs one revert.
**Blast Radius:** app-wide
Every signed-in data flow runs through the changed code: the 8
query-backed collections (tags, results, presets, connections, inbox,
ape keys, custom themes, result-filter-presets) with their new
write/settlement semantics, and every route's background data fetch on
the new `queryClient.query()` API. A divergence in write timing or
live-query reconciliation would surface as slow-closing modals,
rolled-back mutations, or duplicated list rows across the whole
signed-in experience.
---------
Co-authored-by: Jack <jack@monkeytype.com>
Fixes part of #1571.
What
useLiveQuery's rendereddataarray missed updates whenever the underlying collection mutated a row in place. The upstream pipeline does exactly that —flushIncludesStateinpackages/db/src/query/live/collection-config-builder.tswrites newtoArrayinclude values onto the parent row viasetIncludedValue(parentResult, …)without changing its identity. WhensyncDataFromCollectionthen calledSolid's
reconcileshort-circuited onnext === targetat the parent-row level (and again on the include array reference if reached). The reactivestateReactiveMapstill updated correctly —subscribeChangeshandsstate.seta fresh row reference per change event — but thedataarray fell out of sync.The reporter @Tarmean documented this for
toArrayincludes specifically, withflushIncludesStatecloning rows for the events but mutating the rows the collection itself returns from.values().How
syncDataFromCollectionnow shallow-clones each row (and any array-valued field on it) before handing the snapshot toreconcile. The wrapper is small and lives next to the sync helper so the rationale is local:Field-level diffing then actually runs and
toArrayinclude arrays propagate. Non-object values (primitives) pass through; arrays at the top level pass through (reconcilealready iterates them); object values get a one-level copy with arrays inside them also copied so the include-array reference changes.The
stateReactiveMappath is untouched —subscribeChangeswas already getting fresh row references per change event, so only theArray.from(currentCollection.values())sync needed help.Tests
Adds
toArray include reactivity (#1571)describe block inpackages/solid-db/tests/useLiveQuery.test.tsx:projects,issues) and auseLiveQuerythat projects{ id, name, issueTitles: toArray(...) }.issueTitlesarray grows from['Bug in Alpha']to['Bug in Alpha', 'Feature for Alpha']in the rendereddataarray (not just in thestatemap).Without the clone, the rendered
dataarray stays at the original include content; the test would fail onexpect(alphaAfter.issueTitles.length).toBe(2). All 39 tests inpackages/solid-db/tests/useLiveQuery.test.tsxpass. Lint clean (per AGENTS.md).Scope
This addresses the clone-in-reconcile half of #1571. @Tarmean's report flagged a second, related case:
That one lives at
packages/db/src/query/live/collection-config-builder.ts:1851-1867and is a different code path (proxy attachment, not consumer-side reconcile). Happy to file it as a separate issue and take a look if maintainers agree it's the same bug class.Notes for maintainers
flushIncludesStateso include writes produce new parent row identities — happy to switch directions on guidance.state.values()instead ofcurrentCollection.values()(sincestatealready holds fresh references persubscribeChangesevent), butstate's insertion order doesn't match the collection's sort order on inserts, so thedataarray would drift. Cloning preserved the sort order fromcurrentCollection.values().Changeset
@tanstack/solid-db: patchSummary by CodeRabbit
Bug Fixes
toArray-based includes update as expected, including cases where an initially empty include expands after the first matching child insert.