From f9971a05037877269dd0a27d2ffc326e9639bd3a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 01:06:25 +0000 Subject: [PATCH 1/3] fix(db): handle gcTime Infinity to prevent immediate garbage collection When gcTime was set to Infinity, setTimeout(fn, Infinity) would coerce Infinity to 0 via JavaScript's ToInt32 conversion, causing immediate garbage collection instead of disabling it. This fix treats Infinity (and any non-finite values) the same as 0, effectively disabling automatic garbage collection when the user intends for data to never be collected. Fixes issue where collections in Electron apps would unexpectedly lose data when navigating back and forth with gcTime set to Infinity. --- packages/db/src/collection/lifecycle.ts | 6 ++++-- .../db/tests/collection-lifecycle.test.ts | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/db/src/collection/lifecycle.ts b/packages/db/src/collection/lifecycle.ts index ee5fed5e7..b498c546e 100644 --- a/packages/db/src/collection/lifecycle.ts +++ b/packages/db/src/collection/lifecycle.ts @@ -180,8 +180,10 @@ export class CollectionLifecycleManager< const gcTime = this.config.gcTime ?? 300000 // 5 minutes default - // If gcTime is 0, GC is disabled - if (gcTime === 0) { + // If gcTime is 0 or Infinity (or any non-finite value), GC is disabled. + // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC, + // so we must explicitly check for non-finite values here. + if (gcTime === 0 || !Number.isFinite(gcTime)) { return } diff --git a/packages/db/tests/collection-lifecycle.test.ts b/packages/db/tests/collection-lifecycle.test.ts index ef5a04a98..8b70b3ddc 100644 --- a/packages/db/tests/collection-lifecycle.test.ts +++ b/packages/db/tests/collection-lifecycle.test.ts @@ -360,6 +360,26 @@ describe(`Collection Lifecycle Management`, () => { expect(mockSetTimeout).not.toHaveBeenCalled() expect(collection.status).not.toBe(`cleaned-up`) }) + + it(`should disable GC when gcTime is Infinity`, () => { + const collection = createCollection<{ id: string; name: string }>({ + id: `infinity-gc-test`, + getKey: (item) => item.id, + gcTime: Infinity, // Disabled GC via Infinity + sync: { + sync: () => {}, + }, + }) + + const subscription = collection.subscribeChanges(() => {}) + subscription.unsubscribe() + + // Should not start any timer when gcTime is Infinity + // Note: Without this fix, setTimeout(fn, Infinity) would coerce to 0, + // causing immediate GC instead of never collecting + expect(mockSetTimeout).not.toHaveBeenCalled() + expect(collection.status).not.toBe(`cleaned-up`) + }) }) describe(`Manual Preload and Cleanup`, () => { From 4c988dfadf7b0a55c392403b4098e0aaa2ab42fc Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Wed, 14 Jan 2026 18:43:03 -0700 Subject: [PATCH 2/3] chore: add changeset for gcTime Infinity fix Co-Authored-By: Claude Opus 4.5 --- .changeset/fix-gc-infinity.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-gc-infinity.md diff --git a/.changeset/fix-gc-infinity.md b/.changeset/fix-gc-infinity.md new file mode 100644 index 000000000..1421e425e --- /dev/null +++ b/.changeset/fix-gc-infinity.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db': patch +--- + +Fix `gcTime: Infinity` causing immediate garbage collection instead of disabling GC. JavaScript's `setTimeout` coerces `Infinity` to `0` via ToInt32, so we now explicitly check for non-finite values. From ed3cd7b5995a30753ed66a46e60382a91b9dac6c Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Tue, 20 Jan 2026 09:27:42 -0700 Subject: [PATCH 3/3] Handle negative gcTime values in GC timer Negative gcTime values like -1000 would pass through to setTimeout, which browsers treat as 0 (immediate execution). Now gcTime <= 0 disables GC, matching the intent of invalid timeout values. Co-Authored-By: Claude Opus 4.5 --- packages/db/src/collection/lifecycle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/db/src/collection/lifecycle.ts b/packages/db/src/collection/lifecycle.ts index b498c546e..097d138c1 100644 --- a/packages/db/src/collection/lifecycle.ts +++ b/packages/db/src/collection/lifecycle.ts @@ -180,10 +180,10 @@ export class CollectionLifecycleManager< const gcTime = this.config.gcTime ?? 300000 // 5 minutes default - // If gcTime is 0 or Infinity (or any non-finite value), GC is disabled. + // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled. // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC, // so we must explicitly check for non-finite values here. - if (gcTime === 0 || !Number.isFinite(gcTime)) { + if (gcTime <= 0 || !Number.isFinite(gcTime)) { return }