Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f1dc604
fix(db): sanitize recursive source metadata before callbacks
Oct 30, 2026
baa2737
chore: add callback metadata fix changeset
Oct 30, 2026
a30256c
Preserve inline metadata while sanitizing callback sources
Oct 30, 2026
9bd623c
test(db): preserve named array fields in callback recorder
Oct 30, 2026
5ba869f
fix(db): separate compilation cache publication modes
Oct 30, 2026
c63beb0
fix(db): isolate callback metadata and ordered cache windows
Oct 30, 2026
8651096
test(db): inspect compiler result data slots
Oct 30, 2026
8464ec9
test(db): scope replay error order to subscriptions
Oct 30, 2026
c33e18f
fix(db): retain all ordered joined dependencies
Oct 30, 2026
879b70c
test(db): retain sibling replay failure identities
Oct 30, 2026
1e62c7a
refactor(db): reduce compiler shipped size
Oct 30, 2026
ce555e7
Merge origin/main into codex/callback-metadata
Oct 30, 2026
5af7624
refactor(db): simplify correlation handling
Oct 30, 2026
bcdf5a1
fix(db): retain new demand failures during shared replay
Oct 30, 2026
72c8ef1
Scope replay request-error oracle to each subscription
Oct 30, 2026
fa5df11
Merge origin/main into codex/callback-metadata
Nov 2, 2026
a2e75c3
fix(solid-db): keep source row reads outside query dependencies
Nov 2, 2026
0b53825
fix(db): enroll new demand in sibling replay
Nov 2, 2026
72990f8
test: port final overwrite and deferred replay coverage
Nov 2, 2026
ee4e98d
docs: clarify controlled replay evidence scope
Nov 2, 2026
456640a
Merge commit '035a5260ef994ce3aabed77a3f09b35c71482a9a' into codex/ca…
Nov 2, 2026
e9864bc
refactor(db): share smaller replay acquisition paths
Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-correlated-callback-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/db': patch
---

Fix reserved metadata before `fn.select()`, `fn.where()`, and `fn.having()` callbacks in live queries, Effects, and correlated includes. Preserve genuine source metadata, authored inline values, and selected output when compiled queries share a cache. Preserve every ordered window and joined-source publication dependency when one source feeds multiple compiled views or nested queries.
5 changes: 5 additions & 0 deletions .changeset/fix-solid-query-input-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-db': patch
---

Prevent native source row reads during query compilation from recreating the Solid framework binding. Keep actual callback and config query parameters reactive.
5 changes: 5 additions & 0 deletions .changeset/retain-loading-sibling-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/db': patch
---

Keep the last complete live-query result while a new include demand loads under a sibling subscription's truncate replay. The demand now joins the publication barrier when it starts, so a sibling that settles first cannot publish an incomplete replacement. A failed held demand keeps that result until its own coverage recovers or its demand retires.
225 changes: 204 additions & 21 deletions docs/contributing/oracle-coverage.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ No follow-up PR is claimed until one is actually created.
callbacks. The approved callback law remains normative. The accepted runtime
owner has 377 passes / 203 intended REDs and all 2,153 cells; the bounded
concrete lane has the same seven failures and one compatible pass on main.
The runtime follow-up restores this owner and adds direct child receivers.
It reproduces 348 REDs before repair and passes all 979 tests afterward, with
all 3,510 states reached. This includes a low-level shared compiler-cache
receiver whose 132 histories reproduced 26 failures before its cache repair.
Inline QueryRef histories also reject an overbroad
first repair before the corrected production edit. See [the runtime repair](callback-metadata.md).
This does not close the separate static metadata receiving-type failures below.
- P11/P12: raw undefined becomes null and selected empty records disappear.
Their root/QueryRef runtime owner retains 18 REDs, correlated receiver 72 REDs,
and source-public type receiver 280 REDs. Every declared receiving cell and
Expand Down

Large diffs are not rendered by default.

1,135 changes: 1,135 additions & 0 deletions docs/contributing/oracle-reviews/callback-metadata.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,10 @@ describe(`multi-tab Electric over one persisted database`, () => {
// Every stop kind occurs in a generated order, with a distinct running
// peer. Recovery and catch-up separate the blocks. Four membership toggles
// of an initially absent key restore the original rows before the tail.
// This adds 24 steps to the unchanged 8–24-step arbitrary tail. Removing
// The prefix adds 24 steps to the unchanged 8–24-step arbitrary tail. An
// ending dimension either leaves that final state untouched or adds a
// reset/stop suffix of 4 + 2*tabCount steps (8 or 10), or a lagging-commit
// suffix of 6 + 3*tabCount steps (12 or 15). Removing
// the open delivery, stop, or peer commit loses the law's premise; recovering
// before the peer commit would test a different boundary. Existing pinned
// histories still use the same driver without this prefix.
Expand All @@ -983,30 +986,92 @@ describe(`multi-tab Electric over one persisted database`, () => {
source: fc.nat(2),
key: fc.constantFrom(3, 4),
recovery: fc.nat(1),
ending: fc.constantFrom(
`arbitrary` as const,
`reset` as const,
`stop` as const,
`lagging` as const,
),
stopOrder: fc.shuffledSubarray([0, 1, 2, 3], {
minLength: 4,
maxLength: 4,
}),
steps: fc.array(historyStep, { minLength: 8, maxLength: 24 }),
})
.map(({ tabCount, source, key, recovery, stopOrder, steps }) => {
const tab = source % tabCount
const peer = (tab + 1) % tabCount
return {
.map(
({
tabCount,
steps: [
...stopOrder.flatMap((pick): Array<HistoryStep> => [
{ kind: `server change`, key, pick: 0 },
{ kind: `advance`, tab, pick: 0 },
{ kind: `stop`, tab, pick },
{ kind: `catch up`, tab: peer, pick: 0 },
{ kind: `recover`, tab, pick: recovery },
{ kind: `catch up`, tab, pick: 0 },
]),
...steps,
],
}
})
source,
key,
recovery,
ending,
stopOrder,
steps,
}): GeneratedHistory => {
const tab = source % tabCount
const peer = (tab + 1) % tabCount
return {
tabCount,
steps: [
...stopOrder.flatMap((pick): Array<HistoryStep> => [
{ kind: `server change`, key, pick: 0 },
{ kind: `advance`, tab, pick: 0 },
{ kind: `stop`, tab, pick },
{ kind: `catch up`, tab: peer, pick: 0 },
{ kind: `recover`, tab, pick: recovery },
{ kind: `catch up`, tab, pick: 0 },
]),
...steps,
// Preserve arbitrary final states as well as constructing the rare
// final-reset and stopped-tab boundaries. The constructive endings
// start a fresh handle, recover every tab, then deliver one change
// without committing it. Reset abandons that reservation, so final
// delivery judges its failure before recovery (Laws 2/5). Stop
// instead leaves final catch-up to restart the stopped source.
// The lagging ending first establishes the key, then a peer commits
// two updates and the selected tab commits only the older one. It
// leaves that actual overwrite for final catch-up to repair. A
// server change alone establishes only durable/server divergence.
// Tab roles, key and two/three-tab host vary with this grammar.
// Removing either update or the partial commit loses the overwrite;
// removing advance or reset loses that premise; catching up here
// would move the failure out of the final observation cut.
...(ending === `arbitrary`
? []
: [
{ kind: `deploy` as const },
...Array.from(
{ length: tabCount },
(_, index): Array<HistoryStep> => [
{ kind: `recover`, tab: index, pick: recovery },
{ kind: `catch up`, tab: index, pick: 0 },
],
).flat(),
...(ending === `lagging`
? [
{ kind: `server change` as const, key, pick: 1 },
...Array.from({ length: tabCount }, (_, index) => ({
kind: `catch up` as const,
tab: index,
pick: 0,
})),
{ kind: `server change` as const, key, pick: 1 },
{ kind: `server change` as const, key, pick: 1 },
{ kind: `catch up` as const, tab: peer, pick: 0 },
{ kind: `catch up` as const, tab, pick: 1 },
]
: [
{ kind: `server change` as const, key, pick: 0 },
{ kind: `advance` as const, tab, pick: 0 },
ending === `reset`
? { kind: `peer reset` as const }
: { kind: `stop` as const, tab, pick: stopOrder[0]! },
]),
]),
],
}
},
)
type GeneratedHistory = { tabCount: number; steps: Array<HistoryStep> }

type ServerLog = {
Expand Down Expand Up @@ -1119,13 +1184,52 @@ describe(`multi-tab Electric over one persisted database`, () => {
let serverVersion = 0
generatedReach.beginHistory()

// Reach provenance comes from the server messages the driver delivered,
// not merely a difference between durable rows and the current server.
// A newer peer commit must be overwritten by an older change to that key.
// This model-only provenance is a delivered message's server position and
// committing tab. Snapshot rows start at version zero of their handle;
// change messages retain their exact log version, including deletions.
const durableOrigins = new Map<
number,
{
tab: TabModel
handle: number | undefined
version: number
}
>()
const laggingOverwrites = new Set<number>()
const commit = (model: TabModel) => {
if (model.truncates) durable.clear()
if (model.truncates) {
durable.clear()
durableOrigins.clear()
laggingOverwrites.clear()
}
for (const message of model.open ?? []) {
if (!(`key` in message)) continue
const row = message.value
const version =
log.changes.find((change) => change.message === message)?.version ?? 0
const previous = durableOrigins.get(row.id)
const value =
message.headers.operation === `delete` ? undefined : row.label
if (
previous &&
previous.tab !== model &&
previous.handle === model.handle &&
previous.version > version &&
durable.get(row.id) !== value
)
laggingOverwrites.add(row.id)
if (message.headers.operation === `delete`) durable.delete(row.id)
else durable.set(row.id, row.label)
durableOrigins.set(row.id, {
tab: model,
handle: model.handle,
version,
})
if (durable.get(row.id) === log.rows.get(row.id))
laggingOverwrites.delete(row.id)
}
model.open = undefined
model.truncates = false
Expand Down Expand Up @@ -1497,13 +1601,33 @@ describe(`multi-tab Electric over one persisted database`, () => {
models[index]!.stopped !== undefined ||
models[index]!.failedByReset
) {
generatedReach.reach(`stopped source restarted during final catch-up`)
await recover(index, `restart`)
}
const overwrittenBeforeFinal = [...laggingOverwrites]
const divergentBeforeFinal =
rowsOf(durable).some(({ id, label }) => log.rows.get(id) !== label) ||
rowsOf(log.rows).some(({ id, label }) => durable.get(id) !== label)
await deliver(index, `all`, true)
// A reset-abandoned transaction fails only when delivery tries to
// commit it. Judge Law 5 at that cut before the restart establishes
// Law 2's successful catch-up premise.
await check(`after final delivery to tab ${tabs[index]!.tab}`)
if (
divergentBeforeFinal &&
!models[index]!.failedByReset &&
JSON.stringify(rowsOf(durable)) === JSON.stringify(rowsOf(log.rows))
)
generatedReach.reach(
`durable divergence repaired during final catch-up`,
)
if (
overwrittenBeforeFinal.some(
(id) =>
!laggingOverwrites.has(id) && durable.get(id) === log.rows.get(id),
)
)
generatedReach.reach(`lagging commit rewritten during final catch-up`)
if (models[index]!.failedByReset) {
await recover(index, `restart`)
await deliver(index, `all`, true)
Expand Down Expand Up @@ -1687,6 +1811,9 @@ describe(`multi-tab Electric over one persisted database`, () => {
)

type GeneratedReach =
| `stopped source restarted during final catch-up`
| `durable divergence repaired during final catch-up`
| `lagging commit rewritten during final catch-up`
| `must-refetch delivered`
| `peer commit during an open reserved transaction`
| `source stopped with an open transaction`
Expand All @@ -1709,6 +1836,9 @@ describe(`multi-tab Electric over one persisted database`, () => {

const createGeneratedReach = () =>
createOracleReach<GeneratedReach>(`browser.multi-tab-reserved-history`, [
`stopped source restarted during final catch-up`,
`durable divergence repaired during final catch-up`,
`lagging commit rewritten during final catch-up`,
`must-refetch delivered`,
`peer commit during an open reserved transaction`,
`source stopped with an open transaction`,
Expand Down
Loading
Loading