@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/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/global.d.cts +1 -1
- package/dist/cjs/manual-sync.cjs.map +1 -1
- package/dist/cjs/query.cjs +2 -13
- package/dist/cjs/query.cjs.map +1 -1
- package/dist/cjs/serialization.cjs +3 -0
- package/dist/cjs/serialization.cjs.map +1 -1
- package/dist/cjs/serialization.d.cts +3 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/global.d.ts +1 -1
- package/dist/esm/manual-sync.js.map +1 -1
- package/dist/esm/query.js +2 -13
- package/dist/esm/query.js.map +1 -1
- package/dist/esm/serialization.d.ts +3 -1
- package/dist/esm/serialization.js +3 -0
- package/dist/esm/serialization.js.map +1 -1
- package/package.json +28 -29
- package/src/errors.ts +2 -2
- package/src/global.ts +2 -2
- package/src/index.ts +4 -4
- package/src/manual-sync.ts +11 -11
- package/src/query.ts +36 -60
- package/src/serialization.ts +11 -4
package/src/query.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { QueryObserver, hashKey } from
|
|
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
|
|
8
|
-
import { createWriteUtils } from
|
|
9
|
-
import { serializeLoadSubsetOptions } from
|
|
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
|
|
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
|
|
29
|
-
import type { StandardSchemaV1 } from
|
|
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
|
|
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
|
package/src/serialization.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { IR, LoadSubsetOptions } from
|
|
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
|
|