From 8406103c991e28209193e5c40a569b38908e8586 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 13:59:35 -0600 Subject: [PATCH 1/9] test(query-db): characterize ownership cleanup lifecycle --- .../query-db-collection/tests/query.test.ts | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index c711d661f..48bd13ffd 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -5074,6 +5074,92 @@ describe(`QueryCollection`, () => { }) }) + describe(`ownership lifecycle characterization`, () => { + it(`removes only rows whose final subset owner is unloaded`, async () => { + const queryFn = vi + .fn() + .mockResolvedValueOnce([ + { id: `1`, name: `First only` }, + { id: `2`, name: `Shared` }, + ]) + .mockResolvedValueOnce([ + { id: `2`, name: `Shared` }, + { id: `3`, name: `Second only` }, + ]) + const collection = createCollection( + queryCollectionOptions({ + id: `ownership-overlapping-subsets-test`, + queryClient, + queryKey: [`ownership-overlapping-subsets-test`], + queryFn, + getKey, + syncMode: `on-demand`, + }), + ) + const firstSubset = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`1`, `2`])), + }) + const secondSubset = createLiveQueryCollection({ + query: (q) => + q + .from({ item: collection }) + .where(({ item }) => inArray(item.id, [`2`, `3`])), + }) + + await firstSubset.preload() + await secondSubset.preload() + expect(collection.size).toBe(3) + + await firstSubset.cleanup() + await vi.waitFor(() => { + expect(collection.has(`1`)).toBe(false) + expect(collection.has(`2`)).toBe(true) + expect(collection.has(`3`)).toBe(true) + }) + + await secondSubset.cleanup() + await vi.waitFor(() => { + expect(collection.size).toBe(0) + }) + }) + + it(`stays empty after the final query row is removed and its cache entry is GCed`, async () => { + const queryKey = [`ownership-final-row-cache-gc-test`] + let items: Array = [{ id: `1`, name: `Only row` }] + const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + const collection = createCollection( + queryCollectionOptions({ + id: `ownership-final-row-cache-gc-test`, + queryClient, + queryKey, + queryFn, + getKey, + startSync: true, + }), + ) + + await vi.waitFor(() => { + expect(collection.has(`1`)).toBe(true) + }) + + items = [] + await queryClient.invalidateQueries({ queryKey, exact: true }) + await vi.waitFor(() => { + expect(collection.size).toBe(0) + }) + + queryClient.removeQueries({ queryKey, exact: true }) + await flushPromises() + expect(queryClient.getQueryCache().find({ queryKey })).toBeUndefined() + expect(collection.size).toBe(0) + + await collection.cleanup() + }) + }) + it(`should handle duplicate subset loads correctly (refcount bug)`, async () => { // This test catches Bug 1: missing refcount increment when reusing existing observer // When two subscriptions load the same subset, unloading one should NOT destroy From 4b5ac434806100decb8d29ed3c3b0d626150bbc4 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 14:03:23 -0600 Subject: [PATCH 2/9] test(query-db): cover ownership hydration and cache expiry --- .../query-db-collection/tests/query.test.ts | 129 +++++++++++++++--- 1 file changed, 111 insertions(+), 18 deletions(-) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 48bd13ffd..925197dd3 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -5126,35 +5126,128 @@ describe(`QueryCollection`, () => { }) }) - it(`stays empty after the final query row is removed and its cache entry is GCed`, async () => { - const queryKey = [`ownership-final-row-cache-gc-test`] - let items: Array = [{ id: `1`, name: `Only row` }] - const queryFn = vi.fn().mockImplementation(() => Promise.resolve(items)) + it(`expires the Query cache entry after unload without restoring deleted rows`, async () => { + const expiringQueryClient = new QueryClient({ + defaultOptions: { + queries: { gcTime: 100, retry: false, staleTime: Infinity }, + }, + }) + const queryKey = [`ownership-query-cache-expiry-test`] const collection = createCollection( queryCollectionOptions({ - id: `ownership-final-row-cache-gc-test`, - queryClient, + id: `ownership-query-cache-expiry-test`, + queryClient: expiringQueryClient, queryKey, - queryFn, + queryFn: async () => [{ id: `1`, name: `Only row` }], getKey, - startSync: true, + syncMode: `on-demand`, }), ) - - await vi.waitFor(() => { - expect(collection.has(`1`)).toBe(true) + const liveQuery = createLiveQueryCollection({ + query: (q) => q.from({ item: collection }), }) - items = [] - await queryClient.invalidateQueries({ queryKey, exact: true }) - await vi.waitFor(() => { + await liveQuery.preload() + expect(collection.has(`1`)).toBe(true) + + vi.useFakeTimers() + try { + await liveQuery.cleanup() expect(collection.size).toBe(0) + expect( + expiringQueryClient.getQueryCache().find({ queryKey }), + ).toBeDefined() + + await vi.advanceTimersByTimeAsync(101) + + expect( + expiringQueryClient.getQueryCache().find({ queryKey }), + ).toBeUndefined() + expect(collection.size).toBe(0) + } finally { + vi.useRealTimers() + expiringQueryClient.clear() + } + }) + + it(`hydrates a retained row and deletes it when its query revalidates empty`, async () => { + const queryKey = [`ownership-retained-hydration-test`] + const queryHash = hashKey(queryKey) + const retainedRow: CategorisedItem = { + id: `1`, + name: `Retained row`, + category: `A`, + } + let releaseRevalidation!: () => void + const revalidationReleased = new Promise((resolve) => { + releaseRevalidation = resolve + }) + const queryFn = vi.fn(async () => { + await revalidationReleased + return [] + }) + const baseOptions = queryCollectionOptions({ + id: `ownership-retained-hydration-test`, + queryClient, + queryKey, + queryFn, + getKey: (item) => item.id, + startSync: true, + }) + const originalSync = baseOptions.sync + const metadataHarness = createInMemorySyncMetadataApi< + string | number, + CategorisedItem + >({ + persistedRows: new Map([[retainedRow.id, retainedRow]]), + rowMetadata: new Map([ + [ + retainedRow.id, + { + queryCollection: { owners: { [queryHash]: true } }, + }, + ], + ]), + collectionMetadata: new Map([ + [ + `queryCollection:gc:${queryHash}`, + { queryHash, mode: `until-revalidated` }, + ], + ]), + }) + const collection = createCollection({ + ...baseOptions, + sync: { + sync: (params: Parameters[0]) => { + params.begin({ immediate: true }) + params.write({ type: `insert`, value: retainedRow }) + params.commit() + return originalSync.sync({ + ...params, + metadata: metadataHarness.api, + }) + }, + }, }) - queryClient.removeQueries({ queryKey, exact: true }) - await flushPromises() - expect(queryClient.getQueryCache().find({ queryKey })).toBeUndefined() - expect(collection.size).toBe(0) + expect(collection.has(retainedRow.id)).toBe(true) + expect( + metadataHarness.collectionMetadata.has( + `queryCollection:gc:${queryHash}`, + ), + ).toBe(true) + + releaseRevalidation() + await vi.waitFor(() => { + expect(queryFn).toHaveBeenCalledTimes(1) + expect(collection.has(retainedRow.id)).toBe(false) + }) + expect(metadataHarness.rowMetadata.get(retainedRow.id)).toBeUndefined() + expect( + metadataHarness.collectionMetadata.has( + `queryCollection:gc:${queryHash}`, + ), + ).toBe(false) await collection.cleanup() }) From 852d817ea9edac8b64d981e1b395b33ae99dbac6 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 14:07:48 -0600 Subject: [PATCH 3/9] fix(query-db): remove empty ownership entries --- packages/query-db-collection/src/query.ts | 59 ++++++++++++++----- .../query-db-collection/tests/query.test.ts | 25 +++++++- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 7b4578d9e..6da8d78ab 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -506,6 +506,28 @@ function getLoadSubsetOptionsForMeta( * }) * ) */ +/** @internal Exported from the source module only for ownership invariant tests. */ +export function removeOwnershipRelationship( + rowToQueries: Map>, + queryToRows: Map>, + rowKey: string | number, + hashedQueryKey: string, +): boolean { + const owners = rowToQueries.get(rowKey) + owners?.delete(hashedQueryKey) + if (!owners || owners.size === 0) { + rowToQueries.delete(rowKey) + } + + const ownedRows = queryToRows.get(hashedQueryKey) + ownedRows?.delete(rowKey) + if (!ownedRows || ownedRows.size === 0) { + queryToRows.delete(hashedQueryKey) + } + + return !owners || owners.size === 0 +} + // Overload for when schema is provided and select present export function queryCollectionOptions< T extends StandardSchemaV1, @@ -740,6 +762,10 @@ export function queryCollectionOptions( // queryKey → Set const queryToRows = new Map>() + // Queries whose in-memory ownership is authoritative, including an empty set. + // This preserves the old empty queryToRows entry semantics without retaining it. + const resolvedOwnershipQueries = new Set() + // RowKey → Set const rowToQueries = new Map>() @@ -764,6 +790,7 @@ export function queryCollectionOptions( const queryRefCounts = new Map() const addRowOwner = (rowKey: string | number, hashedQueryKey: string) => { + resolvedOwnershipQueries.add(hashedQueryKey) const owners = rowToQueries.get(rowKey) || new Set() owners.add(hashedQueryKey) rowToQueries.set(rowKey, owners) @@ -775,8 +802,14 @@ export function queryCollectionOptions( } const addRowOwners = (rowKey: string | number, owners: Set) => { + if (owners.size === 0) { + rowToQueries.delete(rowKey) + return + } + rowToQueries.set(rowKey, new Set(owners)) owners.forEach((owner) => { + resolvedOwnershipQueries.add(owner) const ownedRows = queryToRows.get(owner) || new Set() ownedRows.add(rowKey) queryToRows.set(owner, ownedRows) @@ -784,16 +817,13 @@ export function queryCollectionOptions( } const removeRowOwner = (rowKey: string | number, hashedQueryKey: string) => { - const owners = rowToQueries.get(rowKey) || new Set() - owners.delete(hashedQueryKey) - rowToQueries.set(rowKey, owners) - - const ownedRows = - queryToRows.get(hashedQueryKey) || new Set() - ownedRows.delete(rowKey) - queryToRows.set(hashedQueryKey, ownedRows) - - return owners.size === 0 + resolvedOwnershipQueries.add(hashedQueryKey) + return removeOwnershipRelationship( + rowToQueries, + queryToRows, + rowKey, + hashedQueryKey, + ) } const removeQueryOwnership = (hashedQueryKey: string) => { @@ -938,7 +968,7 @@ export function queryCollectionOptions( const getHydratedOwnedRowsForQueryBaseline = (hashedQueryKey: string) => { const knownRows = queryToRows.get(hashedQueryKey) - if (knownRows) { + if (knownRows || resolvedOwnershipQueries.has(hashedQueryKey)) { return new Set(knownRows) } @@ -971,14 +1001,14 @@ export function queryCollectionOptions( > => { const knownRows = queryToRows.get(hashedQueryKey) if ( - knownRows && - Array.from(knownRows).every((rowKey) => collection.has(rowKey)) + (knownRows || resolvedOwnershipQueries.has(hashedQueryKey)) && + Array.from(knownRows ?? []).every((rowKey) => collection.has(rowKey)) ) { const baseline = new Map< string | number, { value: any; owners: Set } >() - knownRows.forEach((rowKey) => { + knownRows?.forEach((rowKey) => { const value = collection.get(rowKey) const owners = rowToQueries.get(rowKey) if (value && owners) { @@ -1636,6 +1666,7 @@ export function queryCollectionOptions( state.observers.delete(hashedQueryKey) queryToRows.delete(hashedQueryKey) + resolvedOwnershipQueries.delete(hashedQueryKey) hashToQueryKey.delete(hashedQueryKey) queryRefCounts.delete(hashedQueryKey) effectivePersistedGcTimes.delete(hashedQueryKey) diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 925197dd3..a65611744 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -20,7 +20,10 @@ import { stripVirtualProps, } from '../../db/tests/utils' import { persistedCollectionOptions } from '../../db-sqlite-persistence-core/src' -import { queryCollectionOptions } from '../src/query' +import { + queryCollectionOptions, + removeOwnershipRelationship, +} from '../src/query' import type { QueryFunctionContext } from '@tanstack/query-core' import type { Collection, @@ -5075,6 +5078,26 @@ describe(`QueryCollection`, () => { }) describe(`ownership lifecycle characterization`, () => { + it(`removes empty ownership entries with the final relationship`, () => { + const rowToQueries = new Map>([ + [`row`, new Set([`query`])], + ]) + const queryToRows = new Map>([ + [`query`, new Set([`row`])], + ]) + + expect( + removeOwnershipRelationship( + rowToQueries, + queryToRows, + `row`, + `query`, + ), + ).toBe(true) + expect(rowToQueries.has(`row`)).toBe(false) + expect(queryToRows.has(`query`)).toBe(false) + }) + it(`removes only rows whose final subset owner is unloaded`, async () => { const queryFn = vi .fn() From 01459d1718a8705cb663dbadf8576899f253d958 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 14:13:30 -0600 Subject: [PATCH 4/9] fix(query-db): bound ownership resolution tracking --- packages/query-db-collection/src/query.ts | 20 +++-- .../query-db-collection/tests/query.test.ts | 78 +++++++++++-------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 6da8d78ab..5707f3c34 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -506,8 +506,7 @@ function getLoadSubsetOptionsForMeta( * }) * ) */ -/** @internal Exported from the source module only for ownership invariant tests. */ -export function removeOwnershipRelationship( +function removeOwnershipRelationship( rowToQueries: Map>, queryToRows: Map>, rowKey: string | number, @@ -790,7 +789,6 @@ export function queryCollectionOptions( const queryRefCounts = new Map() const addRowOwner = (rowKey: string | number, hashedQueryKey: string) => { - resolvedOwnershipQueries.add(hashedQueryKey) const owners = rowToQueries.get(rowKey) || new Set() owners.add(hashedQueryKey) rowToQueries.set(rowKey, owners) @@ -809,7 +807,6 @@ export function queryCollectionOptions( rowToQueries.set(rowKey, new Set(owners)) owners.forEach((owner) => { - resolvedOwnershipQueries.add(owner) const ownedRows = queryToRows.get(owner) || new Set() ownedRows.add(rowKey) queryToRows.set(owner, ownedRows) @@ -817,7 +814,6 @@ export function queryCollectionOptions( } const removeRowOwner = (rowKey: string | number, hashedQueryKey: string) => { - resolvedOwnershipQueries.add(hashedQueryKey) return removeOwnershipRelationship( rowToQueries, queryToRows, @@ -1416,6 +1412,10 @@ export function queryCollectionOptions( const previouslyOwnedRows = shouldUsePersistedBaseline ? new Set(persistedBaseline.keys()) : getHydratedOwnedRowsForQueryBaseline(hashedQueryKey) + // From this point onward the result, including an empty result, is the + // authoritative ownership baseline until this query is cleaned up. + resolvedOwnershipQueries.add(hashedQueryKey) + const newItemsMap = new Map() newItemsArray.forEach((item) => { const key = getKey(item) @@ -2045,6 +2045,16 @@ export function queryCollectionOptions( } } + if (typeof process !== `undefined` && process.env.NODE_ENV === `test`) { + Object.defineProperty(enhancedInternalSync, `__getOwnershipMapsForTests`, { + value: () => ({ + rowToQueries, + queryToRows, + resolvedOwnershipQueries, + }), + }) + } + // Create write utils using the manual-sync module const writeUtils = createWriteUtils( () => writeContext, diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index a65611744..739e788ab 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -20,10 +20,7 @@ import { stripVirtualProps, } from '../../db/tests/utils' import { persistedCollectionOptions } from '../../db-sqlite-persistence-core/src' -import { - queryCollectionOptions, - removeOwnershipRelationship, -} from '../src/query' +import { queryCollectionOptions } from '../src/query' import type { QueryFunctionContext } from '@tanstack/query-core' import type { Collection, @@ -50,6 +47,30 @@ interface CategorisedItem { const getKey = (item: TestItem) => item.id +type OwnershipMaps = { + rowToQueries: Map> + queryToRows: Map> + resolvedOwnershipQueries: Set +} + +function inspectOwnershipMaps(options: { + sync: { sync: unknown } +}): OwnershipMaps { + const sync = options.sync.sync as { + __getOwnershipMapsForTests?: () => OwnershipMaps + } + const maps = sync.__getOwnershipMapsForTests?.() + if (!maps) { + throw new Error(`Ownership-map test inspection is unavailable`) + } + return maps +} + +function expectNoEmptyOwnershipSets(maps: OwnershipMaps): void { + maps.rowToQueries.forEach((owners) => expect(owners.size).toBeGreaterThan(0)) + maps.queryToRows.forEach((rows) => expect(rows.size).toBeGreaterThan(0)) +} + // Helper to advance timers and allow microtasks to flush const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0)) @@ -5078,26 +5099,6 @@ describe(`QueryCollection`, () => { }) describe(`ownership lifecycle characterization`, () => { - it(`removes empty ownership entries with the final relationship`, () => { - const rowToQueries = new Map>([ - [`row`, new Set([`query`])], - ]) - const queryToRows = new Map>([ - [`query`, new Set([`row`])], - ]) - - expect( - removeOwnershipRelationship( - rowToQueries, - queryToRows, - `row`, - `query`, - ), - ).toBe(true) - expect(rowToQueries.has(`row`)).toBe(false) - expect(queryToRows.has(`query`)).toBe(false) - }) - it(`removes only rows whose final subset owner is unloaded`, async () => { const queryFn = vi .fn() @@ -5109,16 +5110,16 @@ describe(`QueryCollection`, () => { { id: `2`, name: `Shared` }, { id: `3`, name: `Second only` }, ]) - const collection = createCollection( - queryCollectionOptions({ - id: `ownership-overlapping-subsets-test`, - queryClient, - queryKey: [`ownership-overlapping-subsets-test`], - queryFn, - getKey, - syncMode: `on-demand`, - }), - ) + const options = queryCollectionOptions({ + id: `ownership-overlapping-subsets-test`, + queryClient, + queryKey: [`ownership-overlapping-subsets-test`], + queryFn, + getKey, + syncMode: `on-demand`, + }) + const ownershipMaps = inspectOwnershipMaps(options) + const collection = createCollection(options) const firstSubset = createLiveQueryCollection({ query: (q) => q @@ -5142,11 +5143,15 @@ describe(`QueryCollection`, () => { expect(collection.has(`2`)).toBe(true) expect(collection.has(`3`)).toBe(true) }) + expectNoEmptyOwnershipSets(ownershipMaps) await secondSubset.cleanup() await vi.waitFor(() => { expect(collection.size).toBe(0) }) + expectNoEmptyOwnershipSets(ownershipMaps) + expect(ownershipMaps.rowToQueries.size).toBe(0) + expect(ownershipMaps.queryToRows.size).toBe(0) }) it(`expires the Query cache entry after unload without restoring deleted rows`, async () => { @@ -5218,6 +5223,7 @@ describe(`QueryCollection`, () => { startSync: true, }) const originalSync = baseOptions.sync + const ownershipMaps = inspectOwnershipMaps(baseOptions) const metadataHarness = createInMemorySyncMetadataApi< string | number, CategorisedItem @@ -5266,6 +5272,9 @@ describe(`QueryCollection`, () => { expect(collection.has(retainedRow.id)).toBe(false) }) expect(metadataHarness.rowMetadata.get(retainedRow.id)).toBeUndefined() + expectNoEmptyOwnershipSets(ownershipMaps) + expect(ownershipMaps.rowToQueries.size).toBe(0) + expect(ownershipMaps.queryToRows.size).toBe(0) expect( metadataHarness.collectionMetadata.has( `queryCollection:gc:${queryHash}`, @@ -5273,6 +5282,7 @@ describe(`QueryCollection`, () => { ).toBe(false) await collection.cleanup() + expect(ownershipMaps.resolvedOwnershipQueries.size).toBe(0) }) }) From 1a958777be8d078e7d0579fe810b025977289317 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 14:17:51 -0600 Subject: [PATCH 5/9] fix(query-db): clear retained ownership marker --- .superpowers/sdd/task-2-report.md | 68 +++++++++++++++++++ packages/query-db-collection/src/query.ts | 1 + .../query-db-collection/tests/query.test.ts | 15 ++++ 3 files changed, 84 insertions(+) create mode 100644 .superpowers/sdd/task-2-report.md diff --git a/.superpowers/sdd/task-2-report.md b/.superpowers/sdd/task-2-report.md new file mode 100644 index 000000000..bbf6e8f30 --- /dev/null +++ b/.superpowers/sdd/task-2-report.md @@ -0,0 +1,68 @@ +# Task 2 report + +## Design + +Added a precisely typed ownership-relationship removal helper that deletes `rowToQueries` and `queryToRows` entries when their final relationship disappears. Empty persisted-owner input also no longer creates an empty row entry. + +A separate internal `resolvedOwnershipQueries` set records that a query's in-memory ownership has been resolved. Baseline hydration/persistence lookup treats membership in this set exactly as it previously treated an existing empty `queryToRows` set, so deleting the empty map entry cannot trigger fallback reconstruction. Full query cleanup clears this marker alongside all other query tracking. No package-root export or public configuration/API was added. + +The existing characterization coverage continues to verify overlapping ownership, retained hydration/revalidation, cache expiry, metadata cleanup, and exact/prefix invalidation. A new source-module-only invariant test directly proves both ownership maps omit their keys after the final relationship removal. + +## Files changed + +- `packages/query-db-collection/src/query.ts` +- `packages/query-db-collection/tests/query.test.ts` + +## Verification + +- TDD red: `pnpm --filter @tanstack/query-db-collection test -- --runInBand packages/query-db-collection/tests/query.test.ts` — failed as expected because `removeOwnershipRelationship` did not exist. (This invocation also exposed the repository's existing missing `@tanstack/electric-db-collection` type dependency.) +- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts` — 234/234 runtime/type test instances passed; command exited 0, while Vitest reported three unhandled typecheck diagnostics for the missing `@tanstack/electric-db-collection` dependency in sibling `db-collection-e2e` suites. +- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed; files unchanged. +- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 pre-existing warnings and no errors. The lint script's unrelated e2e autofixes were reverted. +- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declaration generation. +- `pnpm --filter @tanstack/query-db-collection test` — all 234 runtime/type test instances passed, but the package command exited 1 because Vitest reported three unhandled typecheck errors: sibling e2e suites cannot resolve `@tanstack/electric-db-collection` in this worktree. +- `git diff --check` — passed before commit. +- Commit hooks (`eslint --fix`) — passed. + +## Commit + +`852d817ea9edac8b64d981e1b395b33ae99dbac6` (`fix(query-db): remove empty ownership entries`) + +## Self-review and concerns + +The change is limited to ownership bookkeeping and fallback selection; row deletion, transactions, metadata writes, invalidation targeting, and observer/refcount behavior are untouched. The internal test helper is exported only from the unlisted source submodule for direct invariant testing and is not re-exported through the package entry point. + +Concern: the required package test command remains red solely because the worktree cannot resolve the existing `@tanstack/electric-db-collection` dependency during Vitest typechecking, although every query-db test and reported type test passed. Untracked pre-existing `PLAN.md` and other `.superpowers` workflow files were not committed. + +## Review follow-up + +Removed the source-module export of the ownership helper. Real collection lifecycle tests now inspect the closure-owned maps through a non-enumerable hook installed only when `NODE_ENV === "test"`; this creates no source export or declaration/API surface. The tests assert no empty sets after overlapping result reconciliation, first/final subset unload, and retained persisted-row revalidation, and assert the authoritative marker is cleared by collection/query cleanup. + +The authoritative-empty marker is now created only after a successful query result has first selected its prior baseline. Generic row addition/removal and persisted-placeholder cleanup no longer create markers. Therefore placeholder expiry cannot leak hashes, hydration of one query cannot mark unrelated persisted owners, and markers remain bounded by actual resolved query lifecycles and are deleted by query cleanup. Missing relationship behavior was left unchanged because callers use the boolean to preserve row deletion behavior; the helper no longer fabricates empty entries. + +Follow-up verification: + +- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed. +- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'ownership lifecycle characterization'` — 6/6 focused runtime/type tests passed with no type errors; Vitest still reported the three unrelated unresolved `@tanstack/electric-db-collection` source diagnostics. +- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 existing warnings and zero errors; unrelated e2e autofixes were reverted. +- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declarations. +- `pnpm --filter @tanstack/query-db-collection test` — all 232 runtime/type test instances passed with no type errors; command exits 1 solely for the same three unresolved `@tanstack/electric-db-collection` source diagnostics. +- `git diff --check` — passed. + +Self-review: production instrumentation is absent outside test mode and has no exported symbol. Placeholder cleanup cannot add authoritative markers, successful result markers are installed only after fallback baseline selection, and cleanup removes them. Ownership-map assertions now exercise closure-owned maps rather than synthetic maps. No new concern beyond the pre-existing missing workspace dependency affecting Vitest's exit status. + +## Final-review follow-up + +Persisted-placeholder cleanup now removes `resolvedOwnershipQueries` only after row ownership, persisted metadata, retention metadata, and the cleanup transaction have completed. This preserves authoritative-empty fallback suppression for the entire placeholder lifetime and avoids clearing the marker if cleanup does not complete. The runtime TTL test now inspects the closure-owned state: before expiry it asserts the marker and both ownership relationships remain; after expiry it asserts the query-to-row entry, row-to-query entry, and marker are all absent. + +Final-review verification: + +- TDD red: `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'should expire retained ttl placeholders while the app stays open'` — failed at the new post-expiry marker assertion (`expected true to be false`), while both ownership-map cleanup assertions passed. +- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed; test formatting updated. +- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'should expire retained ttl placeholders while the app stays open'` — 2/2 runtime/type instances passed with no test type errors; Vitest reported the three pre-existing unresolved `@tanstack/electric-db-collection` sibling-suite diagnostics. +- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts` — 232/232 runtime/type instances passed with no test type errors; same three unrelated diagnostics reported. +- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 pre-existing warnings and zero errors; unrelated e2e autofixes were reverted. +- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declarations. +- `pnpm --filter @tanstack/query-db-collection test` — all 232 runtime/type instances passed with no test type errors; command exited 1 solely because of the same three unresolved sibling-suite `@tanstack/electric-db-collection` diagnostics. + +Self-review: the deletion occurs after `commit()`, so marker authority remains intact until TTL cleanup is complete; no fallback, row-deletion, metadata, or public API behavior was otherwise changed. Concern remains limited to the pre-existing missing workspace dependency that makes the package test command exit nonzero. diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 5707f3c34..f2434640c 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -1101,6 +1101,7 @@ export function queryCollectionOptions( `${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`, ) commit() + resolvedOwnershipQueries.delete(hashedQueryKey) } const schedulePersistedRetentionExpiry = ( diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index 739e788ab..ae4a747e3 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -5883,6 +5883,7 @@ describe(`QueryCollection`, () => { const baseOptions = queryCollectionOptions(config) const originalSync = baseOptions.sync + const ownershipMaps = inspectOwnershipMaps(baseOptions) const metadataHarness = createInMemorySyncMetadataApi< string | number, CategorisedItem @@ -5915,6 +5916,15 @@ describe(`QueryCollection`, () => { await liveQuery.cleanup() + expect( + ownershipMaps.resolvedOwnershipQueries.has(retainedQueryHash), + ).toBe(true) + expect(ownershipMaps.queryToRows.get(retainedQueryHash)).toEqual( + new Set([`1`]), + ) + expect(ownershipMaps.rowToQueries.get(`1`)).toEqual( + new Set([retainedQueryHash]), + ) expect( metadataHarness.collectionMetadata.get( `queryCollection:gc:${retainedQueryHash}`, @@ -5934,6 +5944,11 @@ describe(`QueryCollection`, () => { ), ).toBeUndefined() expect(collection.has(`1`)).toBe(false) + expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(false) + expect(ownershipMaps.rowToQueries.has(`1`)).toBe(false) + expect( + ownershipMaps.resolvedOwnershipQueries.has(retainedQueryHash), + ).toBe(false) } finally { vi.useRealTimers() } From 6dcf603d4e7538e57fe3526dc344f689df2eccf9 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 15:05:18 -0600 Subject: [PATCH 6/9] refactor(query-db): simplify ownership cleanup --- packages/query-db-collection/src/query.ts | 40 ++++++++--------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index f2434640c..4e94ae751 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -506,27 +506,6 @@ function getLoadSubsetOptionsForMeta( * }) * ) */ -function removeOwnershipRelationship( - rowToQueries: Map>, - queryToRows: Map>, - rowKey: string | number, - hashedQueryKey: string, -): boolean { - const owners = rowToQueries.get(rowKey) - owners?.delete(hashedQueryKey) - if (!owners || owners.size === 0) { - rowToQueries.delete(rowKey) - } - - const ownedRows = queryToRows.get(hashedQueryKey) - ownedRows?.delete(rowKey) - if (!ownedRows || ownedRows.size === 0) { - queryToRows.delete(hashedQueryKey) - } - - return !owners || owners.size === 0 -} - // Overload for when schema is provided and select present export function queryCollectionOptions< T extends StandardSchemaV1, @@ -814,12 +793,19 @@ export function queryCollectionOptions( } const removeRowOwner = (rowKey: string | number, hashedQueryKey: string) => { - return removeOwnershipRelationship( - rowToQueries, - queryToRows, - rowKey, - hashedQueryKey, - ) + const owners = rowToQueries.get(rowKey) + owners?.delete(hashedQueryKey) + if (!owners?.size) { + rowToQueries.delete(rowKey) + } + + const ownedRows = queryToRows.get(hashedQueryKey) + ownedRows?.delete(rowKey) + if (!ownedRows?.size) { + queryToRows.delete(hashedQueryKey) + } + + return !owners?.size } const removeQueryOwnership = (hashedQueryKey: string) => { From 29eefca1e9d84b3e2f1623488ef0fa939caf0a6c Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 15:07:56 -0600 Subject: [PATCH 7/9] chore: add query ownership cleanup changeset --- .changeset/clean-query-ownership.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-query-ownership.md diff --git a/.changeset/clean-query-ownership.md b/.changeset/clean-query-ownership.md new file mode 100644 index 000000000..b86854f27 --- /dev/null +++ b/.changeset/clean-query-ownership.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-db-collection': patch +--- + +Clean up empty query ownership state while preserving authoritative empty results and retained-row lifecycle behavior. From 9c3c658259e8b2702b7c83a1d329b5726f6a3d6d Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 13 Jul 2026 15:19:16 -0600 Subject: [PATCH 8/9] chore: remove workflow artifact --- .superpowers/sdd/task-2-report.md | 68 ------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 .superpowers/sdd/task-2-report.md diff --git a/.superpowers/sdd/task-2-report.md b/.superpowers/sdd/task-2-report.md deleted file mode 100644 index bbf6e8f30..000000000 --- a/.superpowers/sdd/task-2-report.md +++ /dev/null @@ -1,68 +0,0 @@ -# Task 2 report - -## Design - -Added a precisely typed ownership-relationship removal helper that deletes `rowToQueries` and `queryToRows` entries when their final relationship disappears. Empty persisted-owner input also no longer creates an empty row entry. - -A separate internal `resolvedOwnershipQueries` set records that a query's in-memory ownership has been resolved. Baseline hydration/persistence lookup treats membership in this set exactly as it previously treated an existing empty `queryToRows` set, so deleting the empty map entry cannot trigger fallback reconstruction. Full query cleanup clears this marker alongside all other query tracking. No package-root export or public configuration/API was added. - -The existing characterization coverage continues to verify overlapping ownership, retained hydration/revalidation, cache expiry, metadata cleanup, and exact/prefix invalidation. A new source-module-only invariant test directly proves both ownership maps omit their keys after the final relationship removal. - -## Files changed - -- `packages/query-db-collection/src/query.ts` -- `packages/query-db-collection/tests/query.test.ts` - -## Verification - -- TDD red: `pnpm --filter @tanstack/query-db-collection test -- --runInBand packages/query-db-collection/tests/query.test.ts` — failed as expected because `removeOwnershipRelationship` did not exist. (This invocation also exposed the repository's existing missing `@tanstack/electric-db-collection` type dependency.) -- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts` — 234/234 runtime/type test instances passed; command exited 0, while Vitest reported three unhandled typecheck diagnostics for the missing `@tanstack/electric-db-collection` dependency in sibling `db-collection-e2e` suites. -- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed; files unchanged. -- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 pre-existing warnings and no errors. The lint script's unrelated e2e autofixes were reverted. -- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declaration generation. -- `pnpm --filter @tanstack/query-db-collection test` — all 234 runtime/type test instances passed, but the package command exited 1 because Vitest reported three unhandled typecheck errors: sibling e2e suites cannot resolve `@tanstack/electric-db-collection` in this worktree. -- `git diff --check` — passed before commit. -- Commit hooks (`eslint --fix`) — passed. - -## Commit - -`852d817ea9edac8b64d981e1b395b33ae99dbac6` (`fix(query-db): remove empty ownership entries`) - -## Self-review and concerns - -The change is limited to ownership bookkeeping and fallback selection; row deletion, transactions, metadata writes, invalidation targeting, and observer/refcount behavior are untouched. The internal test helper is exported only from the unlisted source submodule for direct invariant testing and is not re-exported through the package entry point. - -Concern: the required package test command remains red solely because the worktree cannot resolve the existing `@tanstack/electric-db-collection` dependency during Vitest typechecking, although every query-db test and reported type test passed. Untracked pre-existing `PLAN.md` and other `.superpowers` workflow files were not committed. - -## Review follow-up - -Removed the source-module export of the ownership helper. Real collection lifecycle tests now inspect the closure-owned maps through a non-enumerable hook installed only when `NODE_ENV === "test"`; this creates no source export or declaration/API surface. The tests assert no empty sets after overlapping result reconciliation, first/final subset unload, and retained persisted-row revalidation, and assert the authoritative marker is cleared by collection/query cleanup. - -The authoritative-empty marker is now created only after a successful query result has first selected its prior baseline. Generic row addition/removal and persisted-placeholder cleanup no longer create markers. Therefore placeholder expiry cannot leak hashes, hydration of one query cannot mark unrelated persisted owners, and markers remain bounded by actual resolved query lifecycles and are deleted by query cleanup. Missing relationship behavior was left unchanged because callers use the boolean to preserve row deletion behavior; the helper no longer fabricates empty entries. - -Follow-up verification: - -- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed. -- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'ownership lifecycle characterization'` — 6/6 focused runtime/type tests passed with no type errors; Vitest still reported the three unrelated unresolved `@tanstack/electric-db-collection` source diagnostics. -- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 existing warnings and zero errors; unrelated e2e autofixes were reverted. -- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declarations. -- `pnpm --filter @tanstack/query-db-collection test` — all 232 runtime/type test instances passed with no type errors; command exits 1 solely for the same three unresolved `@tanstack/electric-db-collection` source diagnostics. -- `git diff --check` — passed. - -Self-review: production instrumentation is absent outside test mode and has no exported symbol. Placeholder cleanup cannot add authoritative markers, successful result markers are installed only after fallback baseline selection, and cleanup removes them. Ownership-map assertions now exercise closure-owned maps rather than synthetic maps. No new concern beyond the pre-existing missing workspace dependency affecting Vitest's exit status. - -## Final-review follow-up - -Persisted-placeholder cleanup now removes `resolvedOwnershipQueries` only after row ownership, persisted metadata, retention metadata, and the cleanup transaction have completed. This preserves authoritative-empty fallback suppression for the entire placeholder lifetime and avoids clearing the marker if cleanup does not complete. The runtime TTL test now inspects the closure-owned state: before expiry it asserts the marker and both ownership relationships remain; after expiry it asserts the query-to-row entry, row-to-query entry, and marker are all absent. - -Final-review verification: - -- TDD red: `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'should expire retained ttl placeholders while the app stays open'` — failed at the new post-expiry marker assertion (`expected true to be false`), while both ownership-map cleanup assertions passed. -- `pnpm exec prettier --write packages/query-db-collection/src/query.ts packages/query-db-collection/tests/query.test.ts` — passed; test formatting updated. -- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts -t 'should expire retained ttl placeholders while the app stays open'` — 2/2 runtime/type instances passed with no test type errors; Vitest reported the three pre-existing unresolved `@tanstack/electric-db-collection` sibling-suite diagnostics. -- `cd packages/query-db-collection && pnpm exec vitest run tests/query.test.ts` — 232/232 runtime/type instances passed with no test type errors; same three unrelated diagnostics reported. -- `pnpm --filter @tanstack/query-db-collection lint` — passed with 15 pre-existing warnings and zero errors; unrelated e2e autofixes were reverted. -- `pnpm --filter @tanstack/query-db-collection build` — passed, including ESM/CJS declarations. -- `pnpm --filter @tanstack/query-db-collection test` — all 232 runtime/type instances passed with no test type errors; command exited 1 solely because of the same three unresolved sibling-suite `@tanstack/electric-db-collection` diagnostics. - -Self-review: the deletion occurs after `commit()`, so marker authority remains intact until TTL cleanup is complete; no fallback, row-deletion, metadata, or public API behavior was otherwise changed. Concern remains limited to the pre-existing missing workspace dependency that makes the package test command exit nonzero. From d2d5f8efdfc74482a5e6ee344bba5c857180f8a2 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 16 Jul 2026 14:03:14 -0600 Subject: [PATCH 9/9] refactor: simplify query ownership bookkeeping --- packages/query-db-collection/src/query.ts | 27 ++++++++----------- .../query-db-collection/tests/query.test.ts | 20 +++++--------- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 4e94ae751..8ef7a7ea1 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -737,13 +737,10 @@ export function queryCollectionOptions( // hashedQueryKey → queryKey const hashToQueryKey = new Map() - // queryKey → Set + // queryKey → Set. Entry presence means ownership is resolved; + // an empty set represents a resolved query that currently owns no rows. const queryToRows = new Map>() - // Queries whose in-memory ownership is authoritative, including an empty set. - // This preserves the old empty queryToRows entry semantics without retaining it. - const resolvedOwnershipQueries = new Set() - // RowKey → Set const rowToQueries = new Map>() @@ -801,9 +798,6 @@ export function queryCollectionOptions( const ownedRows = queryToRows.get(hashedQueryKey) ownedRows?.delete(rowKey) - if (!ownedRows?.size) { - queryToRows.delete(hashedQueryKey) - } return !owners?.size } @@ -950,7 +944,7 @@ export function queryCollectionOptions( const getHydratedOwnedRowsForQueryBaseline = (hashedQueryKey: string) => { const knownRows = queryToRows.get(hashedQueryKey) - if (knownRows || resolvedOwnershipQueries.has(hashedQueryKey)) { + if (knownRows) { return new Set(knownRows) } @@ -983,14 +977,14 @@ export function queryCollectionOptions( > => { const knownRows = queryToRows.get(hashedQueryKey) if ( - (knownRows || resolvedOwnershipQueries.has(hashedQueryKey)) && - Array.from(knownRows ?? []).every((rowKey) => collection.has(rowKey)) + knownRows && + Array.from(knownRows).every((rowKey) => collection.has(rowKey)) ) { const baseline = new Map< string | number, { value: any; owners: Set } >() - knownRows?.forEach((rowKey) => { + knownRows.forEach((rowKey) => { const value = collection.get(rowKey) const owners = rowToQueries.get(rowKey) if (value && owners) { @@ -1087,7 +1081,7 @@ export function queryCollectionOptions( `${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`, ) commit() - resolvedOwnershipQueries.delete(hashedQueryKey) + queryToRows.delete(hashedQueryKey) } const schedulePersistedRetentionExpiry = ( @@ -1401,7 +1395,10 @@ export function queryCollectionOptions( : getHydratedOwnedRowsForQueryBaseline(hashedQueryKey) // From this point onward the result, including an empty result, is the // authoritative ownership baseline until this query is cleaned up. - resolvedOwnershipQueries.add(hashedQueryKey) + queryToRows.set( + hashedQueryKey, + queryToRows.get(hashedQueryKey) ?? new Set(), + ) const newItemsMap = new Map() newItemsArray.forEach((item) => { @@ -1653,7 +1650,6 @@ export function queryCollectionOptions( state.observers.delete(hashedQueryKey) queryToRows.delete(hashedQueryKey) - resolvedOwnershipQueries.delete(hashedQueryKey) hashToQueryKey.delete(hashedQueryKey) queryRefCounts.delete(hashedQueryKey) effectivePersistedGcTimes.delete(hashedQueryKey) @@ -2037,7 +2033,6 @@ export function queryCollectionOptions( value: () => ({ rowToQueries, queryToRows, - resolvedOwnershipQueries, }), }) } diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index ae4a747e3..3005db911 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -50,7 +50,6 @@ const getKey = (item: TestItem) => item.id type OwnershipMaps = { rowToQueries: Map> queryToRows: Map> - resolvedOwnershipQueries: Set } function inspectOwnershipMaps(options: { @@ -66,9 +65,8 @@ function inspectOwnershipMaps(options: { return maps } -function expectNoEmptyOwnershipSets(maps: OwnershipMaps): void { +function expectNoEmptyRowOwnershipSets(maps: OwnershipMaps): void { maps.rowToQueries.forEach((owners) => expect(owners.size).toBeGreaterThan(0)) - maps.queryToRows.forEach((rows) => expect(rows.size).toBeGreaterThan(0)) } // Helper to advance timers and allow microtasks to flush @@ -5143,13 +5141,13 @@ describe(`QueryCollection`, () => { expect(collection.has(`2`)).toBe(true) expect(collection.has(`3`)).toBe(true) }) - expectNoEmptyOwnershipSets(ownershipMaps) + expectNoEmptyRowOwnershipSets(ownershipMaps) await secondSubset.cleanup() await vi.waitFor(() => { expect(collection.size).toBe(0) }) - expectNoEmptyOwnershipSets(ownershipMaps) + expectNoEmptyRowOwnershipSets(ownershipMaps) expect(ownershipMaps.rowToQueries.size).toBe(0) expect(ownershipMaps.queryToRows.size).toBe(0) }) @@ -5272,9 +5270,9 @@ describe(`QueryCollection`, () => { expect(collection.has(retainedRow.id)).toBe(false) }) expect(metadataHarness.rowMetadata.get(retainedRow.id)).toBeUndefined() - expectNoEmptyOwnershipSets(ownershipMaps) + expectNoEmptyRowOwnershipSets(ownershipMaps) expect(ownershipMaps.rowToQueries.size).toBe(0) - expect(ownershipMaps.queryToRows.size).toBe(0) + expect(ownershipMaps.queryToRows.get(queryHash)).toEqual(new Set()) expect( metadataHarness.collectionMetadata.has( `queryCollection:gc:${queryHash}`, @@ -5282,7 +5280,6 @@ describe(`QueryCollection`, () => { ).toBe(false) await collection.cleanup() - expect(ownershipMaps.resolvedOwnershipQueries.size).toBe(0) }) }) @@ -5916,9 +5913,7 @@ describe(`QueryCollection`, () => { await liveQuery.cleanup() - expect( - ownershipMaps.resolvedOwnershipQueries.has(retainedQueryHash), - ).toBe(true) + expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(true) expect(ownershipMaps.queryToRows.get(retainedQueryHash)).toEqual( new Set([`1`]), ) @@ -5946,9 +5941,6 @@ describe(`QueryCollection`, () => { expect(collection.has(`1`)).toBe(false) expect(ownershipMaps.queryToRows.has(retainedQueryHash)).toBe(false) expect(ownershipMaps.rowToQueries.has(`1`)).toBe(false) - expect( - ownershipMaps.resolvedOwnershipQueries.has(retainedQueryHash), - ).toBe(false) } finally { vi.useRealTimers() }