Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions packages/db/src/query/live/collection-config-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,7 @@ function createChildCollectionEntry(
},
},
startSync: true,
gcTime: 0,
})

const entry: ChildCollectionEntry = {
Expand Down
51 changes: 50 additions & 1 deletion packages/db/tests/query/includes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
and,
concat,
Expand All @@ -8,6 +8,7 @@ import {
toArray,
} from '../../src/query/index.js'
import { createCollection } from '../../src/collection/index.js'
import { CleanupQueue } from '../../src/collection/cleanup-queue.js'
import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js'

type Project = {
Expand Down Expand Up @@ -4012,4 +4013,52 @@ describe(`includes subqueries`, () => {
})
})
})

describe(`child collection garbage collection`, () => {
beforeEach(() => {
vi.useFakeTimers()
CleanupQueue.resetInstance()
})

afterEach(() => {
vi.useRealTimers()
CleanupQueue.resetInstance()
})

it(`child collections should not be garbage collected when external subscribers unmount`, async () => {
const collection = buildIncludesQuery()
await collection.preload()

// Verify child data exists
const alpha = collection.get(1) as any
expect(childItems(alpha.issues)).toEqual([
{ id: 10, title: `Bug in Alpha` },
{ id: 11, title: `Feature for Alpha` },
])

const beta = collection.get(2) as any
expect(childItems(beta.issues)).toEqual([
{ id: 20, title: `Bug in Beta` },
])

// Simulate what useLiveQuery does in React: subscribe to child collection,
// then unsubscribe when the component unmounts (e.g., virtual table scroll)
const childSub = alpha.issues.subscribeChanges(() => {})
childSub.unsubscribe()

// Advance well past the default gcTime (5 minutes = 300,000ms)
await vi.advanceTimersByTimeAsync(600_000)

// Child collection data should still be intact — the includes system
// owns these collections and manages their lifecycle via flushIncludesState.
// External GC must not destroy them.
expect(childItems(alpha.issues)).toEqual([
{ id: 10, title: `Bug in Alpha` },
{ id: 11, title: `Feature for Alpha` },
])
expect(childItems(beta.issues)).toEqual([
{ id: 20, title: `Bug in Beta` },
])
})
})
})
Loading