@tanstack/query-db-collection 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/query.ts CHANGED
@@ -1,12 +1,13 @@
1
- import { QueryObserver, hashKey } from "@tanstack/query-core"
1
+ import { QueryObserver, hashKey } from '@tanstack/query-core'
2
+ import { deepEquals } from '@tanstack/db'
2
3
  import {
3
4
  GetKeyRequiredError,
4
5
  QueryClientRequiredError,
5
6
  QueryFnRequiredError,
6
7
  QueryKeyRequiredError,
7
- } from "./errors"
8
- import { createWriteUtils } from "./manual-sync"
9
- import { serializeLoadSubsetOptions } from "./serialization"
8
+ } from './errors'
9
+ import { createWriteUtils } from './manual-sync'
10
+ import { serializeLoadSubsetOptions } from './serialization'
10
11
  import type {
11
12
  BaseCollectionConfig,
12
13
  ChangeMessage,
@@ -17,7 +18,7 @@ import type {
17
18
  SyncConfig,
18
19
  UpdateMutationFnParams,
19
20
  UtilsRecord,
20
- } from "@tanstack/db"
21
+ } from '@tanstack/db'
21
22
  import type {
22
23
  FetchStatus,
23
24
  QueryClient,
@@ -25,11 +26,11 @@ import type {
25
26
  QueryKey,
26
27
  QueryObserverOptions,
27
28
  QueryObserverResult,
28
- } from "@tanstack/query-core"
29
- import type { StandardSchemaV1 } from "@standard-schema/spec"
29
+ } from '@tanstack/query-core'
30
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
30
31
 
31
32
  // Re-export for external use
32
- export type { SyncOperation } from "./manual-sync"
33
+ export type { SyncOperation } from './manual-sync'
33
34
 
34
35
  // Schema output type inference helper (matches electric.ts pattern)
35
36
  type InferSchemaOutput<T> = T extends StandardSchemaV1
@@ -59,7 +60,7 @@ type TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey
59
60
  export interface QueryCollectionConfig<
60
61
  T extends object = object,
61
62
  TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (
62
- context: QueryFunctionContext<any>
63
+ context: QueryFunctionContext<any>,
63
64
  ) => Promise<any>,
64
65
  TError = unknown,
65
66
  TQueryKey extends QueryKey = QueryKey,
@@ -71,7 +72,7 @@ export interface QueryCollectionConfig<
71
72
  queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>
72
73
  /** Function that fetches data from the server. Must return the complete collection state */
73
74
  queryFn: TQueryFn extends (
74
- context: QueryFunctionContext<TQueryKey>
75
+ context: QueryFunctionContext<TQueryKey>,
75
76
  ) => Promise<Array<any>>
76
77
  ? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>
77
78
  : TQueryFn
@@ -231,7 +232,7 @@ class QueryCollectionUtilsImpl {
231
232
  constructor(
232
233
  state: QueryCollectionState,
233
234
  refetch: RefetchFn,
234
- writeUtils: ReturnType<typeof createWriteUtils>
235
+ writeUtils: ReturnType<typeof createWriteUtils>,
235
236
  ) {
236
237
  this.state = state
237
238
  this.refetchFn = refetch
@@ -269,21 +270,21 @@ class QueryCollectionUtilsImpl {
269
270
  public get isFetching() {
270
271
  // check if any observer is fetching
271
272
  return Array.from(this.state.observers.values()).some(
272
- (observer) => observer.getCurrentResult().isFetching
273
+ (observer) => observer.getCurrentResult().isFetching,
273
274
  )
274
275
  }
275
276
 
276
277
  public get isRefetching() {
277
278
  // check if any observer is refetching
278
279
  return Array.from(this.state.observers.values()).some(
279
- (observer) => observer.getCurrentResult().isRefetching
280
+ (observer) => observer.getCurrentResult().isRefetching,
280
281
  )
281
282
  }
282
283
 
283
284
  public get isLoading() {
284
285
  // check if any observer is loading
285
286
  return Array.from(this.state.observers.values()).some(
286
- (observer) => observer.getCurrentResult().isLoading
287
+ (observer) => observer.getCurrentResult().isLoading,
287
288
  )
288
289
  }
289
290
 
@@ -292,14 +293,14 @@ class QueryCollectionUtilsImpl {
292
293
  return Math.max(
293
294
  0,
294
295
  ...Array.from(this.state.observers.values()).map(
295
- (observer) => observer.getCurrentResult().dataUpdatedAt
296
- )
296
+ (observer) => observer.getCurrentResult().dataUpdatedAt,
297
+ ),
297
298
  )
298
299
  }
299
300
 
300
301
  public get fetchStatus(): Array<FetchStatus> {
301
302
  return Array.from(this.state.observers.values()).map(
302
- (observer) => observer.getCurrentResult().fetchStatus
303
+ (observer) => observer.getCurrentResult().fetchStatus,
303
304
  )
304
305
  }
305
306
  }
@@ -408,7 +409,7 @@ export function queryCollectionOptions<
408
409
  > & {
409
410
  schema: T
410
411
  select: (data: TQueryData) => Array<InferSchemaInput<T>>
411
- }
412
+ },
412
413
  ): CollectionConfig<
413
414
  InferSchemaOutput<T>,
414
415
  TKey,
@@ -428,7 +429,7 @@ export function queryCollectionOptions<
428
429
  export function queryCollectionOptions<
429
430
  T extends object,
430
431
  TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (
431
- context: QueryFunctionContext<any>
432
+ context: QueryFunctionContext<any>,
432
433
  ) => Promise<any>,
433
434
  TError = unknown,
434
435
  TQueryKey extends QueryKey = QueryKey,
@@ -446,7 +447,7 @@ export function queryCollectionOptions<
446
447
  > & {
447
448
  schema?: never // prohibit schema
448
449
  select: (data: TQueryData) => Array<T>
449
- }
450
+ },
450
451
  ): CollectionConfig<
451
452
  T,
452
453
  TKey,
@@ -467,7 +468,7 @@ export function queryCollectionOptions<
467
468
  config: QueryCollectionConfig<
468
469
  InferSchemaOutput<T>,
469
470
  (
470
- context: QueryFunctionContext<any>
471
+ context: QueryFunctionContext<any>,
471
472
  ) => Promise<Array<InferSchemaOutput<T>>>,
472
473
  TError,
473
474
  TQueryKey,
@@ -475,7 +476,7 @@ export function queryCollectionOptions<
475
476
  T
476
477
  > & {
477
478
  schema: T
478
- }
479
+ },
479
480
  ): CollectionConfig<
480
481
  InferSchemaOutput<T>,
481
482
  TKey,
@@ -506,7 +507,7 @@ export function queryCollectionOptions<
506
507
  TKey
507
508
  > & {
508
509
  schema?: never // prohibit schema
509
- }
510
+ },
510
511
  ): CollectionConfig<
511
512
  T,
512
513
  TKey,
@@ -518,7 +519,7 @@ export function queryCollectionOptions<
518
519
  }
519
520
 
520
521
  export function queryCollectionOptions(
521
- config: QueryCollectionConfig<Record<string, unknown>>
522
+ config: QueryCollectionConfig<Record<string, unknown>>,
522
523
  ): CollectionConfig<
523
524
  Record<string, unknown>,
524
525
  string | number,
@@ -662,7 +663,7 @@ export function queryCollectionOptions(
662
663
 
663
664
  const createQueryFromOpts = (
664
665
  opts: LoadSubsetOptions = {},
665
- queryFunction: typeof queryFn = queryFn
666
+ queryFunction: typeof queryFn = queryFn,
666
667
  ): true | Promise<void> => {
667
668
  // Generate key using common function
668
669
  const key = generateQueryKeyFromOptions(opts)
@@ -674,7 +675,7 @@ export function queryCollectionOptions(
674
675
  // Increment reference count since another consumer is using this observer
675
676
  queryRefCounts.set(
676
677
  hashedQueryKey,
677
- (queryRefCounts.get(hashedQueryKey) || 0) + 1
678
+ (queryRefCounts.get(hashedQueryKey) || 0) + 1,
678
679
  )
679
680
 
680
681
  // Get the current result and return based on its state
@@ -738,7 +739,7 @@ export function queryCollectionOptions(
738
739
  // Increment reference count for this query
739
740
  queryRefCounts.set(
740
741
  hashedQueryKey,
741
- (queryRefCounts.get(hashedQueryKey) || 0) + 1
742
+ (queryRefCounts.get(hashedQueryKey) || 0) + 1,
742
743
  )
743
744
 
744
745
  // Create a promise that resolves when the query result is first available
@@ -789,7 +790,7 @@ export function queryCollectionOptions(
789
790
  }
790
791
 
791
792
  const currentSyncedItems: Map<string | number, any> = new Map(
792
- collection._state.syncedData.entries()
793
+ collection._state.syncedData.entries(),
793
794
  )
794
795
  const newItemsMap = new Map<string | number, any>()
795
796
  newItemsArray.forEach((item) => {
@@ -799,26 +800,6 @@ export function queryCollectionOptions(
799
800
 
800
801
  begin()
801
802
 
802
- // Helper function for shallow equality check of objects
803
- const shallowEqual = (
804
- obj1: Record<string, any>,
805
- obj2: Record<string, any>
806
- ): boolean => {
807
- // Get all keys from both objects
808
- const keys1 = Object.keys(obj1)
809
- const keys2 = Object.keys(obj2)
810
-
811
- // If number of keys is different, objects are not equal
812
- if (keys1.length !== keys2.length) return false
813
-
814
- // Check if all keys in obj1 have the same values in obj2
815
- return keys1.every((key) => {
816
- // Skip comparing functions and complex objects deeply
817
- if (typeof obj1[key] === `function`) return true
818
- return obj1[key] === obj2[key]
819
- })
820
- }
821
-
822
803
  currentSyncedItems.forEach((oldItem, key) => {
823
804
  const newItem = newItemsMap.get(key)
824
805
  if (!newItem) {
@@ -826,12 +807,7 @@ export function queryCollectionOptions(
826
807
  if (needToRemove) {
827
808
  write({ type: `delete`, value: oldItem })
828
809
  }
829
- } else if (
830
- !shallowEqual(
831
- oldItem as Record<string, any>,
832
- newItem as Record<string, any>
833
- )
834
- ) {
810
+ } else if (!deepEquals(oldItem, newItem)) {
835
811
  // Only update if there are actual differences in the properties
836
812
  write({ type: `update`, value: newItem })
837
813
  }
@@ -857,7 +833,7 @@ export function queryCollectionOptions(
857
833
 
858
834
  console.error(
859
835
  `[QueryCollection] Error observing query ${String(queryKey)}:`,
860
- result.error
836
+ result.error,
861
837
  )
862
838
 
863
839
  // Mark collection as ready even on error to avoid blocking apps
@@ -873,7 +849,7 @@ export function queryCollectionOptions(
873
849
 
874
850
  const subscribeToQuery = (
875
851
  observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,
876
- hashedQueryKey: string
852
+ hashedQueryKey: string,
877
853
  ) => {
878
854
  if (!isSubscribed(hashedQueryKey)) {
879
855
  const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!
@@ -913,7 +889,7 @@ export function queryCollectionOptions(
913
889
  } else if (subscriberCount === 0) {
914
890
  unsubscribeFromQueries()
915
891
  }
916
- }
892
+ },
917
893
  )
918
894
 
919
895
  // If syncMode is eager, create the initial query without any predicates
@@ -1013,7 +989,7 @@ export function queryCollectionOptions(
1013
989
  if (refcount > 0) {
1014
990
  console.warn(
1015
991
  `[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,
1016
- { hashedQueryKey }
992
+ { hashedQueryKey },
1017
993
  )
1018
994
  }
1019
995
 
@@ -1064,7 +1040,7 @@ export function queryCollectionOptions(
1064
1040
  allQueryKeys.map(async (qKey) => {
1065
1041
  await queryClient.cancelQueries({ queryKey: qKey, exact: true })
1066
1042
  queryClient.removeQueries({ queryKey: qKey, exact: true })
1067
- })
1043
+ }),
1068
1044
  )
1069
1045
  }
1070
1046
 
@@ -1186,7 +1162,7 @@ export function queryCollectionOptions(
1186
1162
 
1187
1163
  // Create write utils using the manual-sync module
1188
1164
  const writeUtils = createWriteUtils<any, string | number, any>(
1189
- () => writeContext
1165
+ () => writeContext,
1190
1166
  )
1191
1167
 
1192
1168
  // Create wrapper handlers for direct persistence operations that handle refetching
@@ -1,11 +1,13 @@
1
- import type { IR, LoadSubsetOptions } from "@tanstack/db"
1
+ import type { IR, LoadSubsetOptions } from '@tanstack/db'
2
2
 
3
3
  /**
4
- * Serializes LoadSubsetOptions into a stable, hashable format for query keys
4
+ * Serializes LoadSubsetOptions into a stable, hashable format for query keys.
5
+ * Includes where, orderBy, limit, and offset for pagination support.
6
+ * Note: cursor expressions are not serialized as they are backend-specific.
5
7
  * @internal
6
8
  */
7
9
  export function serializeLoadSubsetOptions(
8
- options: LoadSubsetOptions | undefined
10
+ options: LoadSubsetOptions | undefined,
9
11
  ): string | undefined {
10
12
  if (!options) {
11
13
  return undefined
@@ -43,6 +45,11 @@ export function serializeLoadSubsetOptions(
43
45
  result.limit = options.limit
44
46
  }
45
47
 
48
+ // Include offset for pagination support
49
+ if (options.offset !== undefined) {
50
+ result.offset = options.offset
51
+ }
52
+
46
53
  return Object.keys(result).length === 0 ? undefined : JSON.stringify(result)
47
54
  }
48
55
 
@@ -120,7 +127,7 @@ function serializeValue(value: unknown): unknown {
120
127
  Object.entries(value as Record<string, unknown>).map(([key, val]) => [
121
128
  key,
122
129
  serializeValue(val),
123
- ])
130
+ ]),
124
131
  )
125
132
  }
126
133