Skip to content

Commit 35fcc2c

Browse files
committed
fix(electric): combine cursor expressions into single requestSnapshot
Instead of making two separate requestSnapshot calls (one for whereFrom, one for whereCurrent), combine them using OR into a single request. This avoids potential issues with multiple sequential snapshot requests that were causing timeouts in CI. The combined expression (whereFrom OR whereCurrent) matches the original behavior where cursor was combined with the where clause.
1 parent 86366b9 commit 35fcc2c

File tree

2 files changed

+16
-30
lines changed

2 files changed

+16
-30
lines changed

packages/electric-db-collection/src/electric.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from "@electric-sql/client"
77
import { Store } from "@tanstack/store"
88
import DebugModule from "debug"
9-
import { DeduplicatedLoadSubset, and } from "@tanstack/db"
9+
import { DeduplicatedLoadSubset, and, or } from "@tanstack/db"
1010
import {
1111
ExpectedNumberInAwaitTxIdError,
1212
StreamAbortedError,
@@ -393,38 +393,24 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
393393
const { cursor, where, orderBy, limit } = opts
394394

395395
if (cursor) {
396-
// Make sequential requests for cursor-based pagination
397-
// Note: requests are sequential to avoid potential issues with concurrent snapshots
398-
399-
// Request 1: Rows matching whereFrom (rows > cursor, with limit)
400-
// Combine main where with cursor.whereFrom
401-
const whereFromOpts: LoadSubsetOptions = {
402-
where: where ? and(where, cursor.whereFrom) : cursor.whereFrom,
396+
// Combine whereFrom and whereCurrent into a single request using OR
397+
// This gets: (rows > cursor) OR (rows = cursor for ties)
398+
// Using a single request avoids potential issues with multiple sequential snapshots
399+
const combinedCursor = or(cursor.whereFrom, cursor.whereCurrent)
400+
const cursorOpts: LoadSubsetOptions = {
401+
where: where ? and(where, combinedCursor) : combinedCursor,
403402
orderBy,
403+
// Note: limit applies to combined result, which may include ties
404+
// This matches the original behavior where cursor was combined with where
404405
limit,
405406
}
406-
const whereFromParams = compileSQL<T>(whereFromOpts)
407-
408-
debug(
409-
`${collectionId ? `[${collectionId}] ` : ``}Requesting cursor.whereFrom snapshot (with limit ${limit})`
410-
)
411-
412-
await stream.requestSnapshot(whereFromParams)
413-
414-
// Request 2: All rows matching whereCurrent (ties at boundary, no limit)
415-
// Combine main where with cursor.whereCurrent
416-
const whereCurrentOpts: LoadSubsetOptions = {
417-
where: where ? and(where, cursor.whereCurrent) : cursor.whereCurrent,
418-
orderBy,
419-
// No limit - get all ties
420-
}
421-
const whereCurrentParams = compileSQL<T>(whereCurrentOpts)
407+
const cursorParams = compileSQL<T>(cursorOpts)
422408

423409
debug(
424-
`${collectionId ? `[${collectionId}] ` : ``}Requesting cursor.whereCurrent snapshot (all ties)`
410+
`${collectionId ? `[${collectionId}] ` : ``}Requesting cursor snapshot (whereFrom OR whereCurrent, limit ${limit})`
425411
)
426412

427-
await stream.requestSnapshot(whereCurrentParams)
413+
await stream.requestSnapshot(cursorParams)
428414
} else {
429415
// No cursor - standard single request
430416
const snapshotParams = compileSQL<T>(opts)

packages/electric-db-collection/tests/electric-live-query.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,8 +1198,8 @@ describe(`Electric Collection - loadSubset deduplication`, () => {
11981198
await new Promise((resolve) => setTimeout(resolve, 0))
11991199

12001200
// The existing live query re-requests its data after truncate
1201-
// After must-refetch, the query requests data again (1 initial + 2 after truncate)
1202-
expect(mockRequestSnapshot).toHaveBeenCalledTimes(3)
1201+
// After must-refetch, the query requests data again (1 initial + 1 after truncate)
1202+
expect(mockRequestSnapshot).toHaveBeenCalledTimes(2)
12031203

12041204
// Create the same live query again after reset
12051205
// This should NOT be deduped because the reset cleared the deduplication state,
@@ -1218,8 +1218,8 @@ describe(`Electric Collection - loadSubset deduplication`, () => {
12181218
await new Promise((resolve) => setTimeout(resolve, 0))
12191219

12201220
// Should have more calls - the different query triggered a new request
1221-
// 1 initial + 2 after must-refetch + 1 for new query = 4
1222-
expect(mockRequestSnapshot).toHaveBeenCalledTimes(4)
1221+
// 1 initial + 1 after must-refetch + 1 for new query = 3
1222+
expect(mockRequestSnapshot).toHaveBeenCalledTimes(3)
12231223
})
12241224

12251225
it(`should deduplicate unlimited queries regardless of orderBy`, async () => {

0 commit comments

Comments
 (0)