@tanstack/query-db-collection 1.0.6 → 1.0.8
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 +5 -1
- package/dist/cjs/manual-sync.cjs.map +1 -1
- package/dist/cjs/manual-sync.d.cts +6 -0
- package/dist/cjs/query.cjs +42 -2
- 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.d.ts +6 -0
- package/dist/esm/manual-sync.js +5 -1
- package/dist/esm/manual-sync.js.map +1 -1
- package/dist/esm/query.js +42 -2
- 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 -28
- package/src/errors.ts +2 -2
- package/src/global.ts +2 -2
- package/src/index.ts +4 -4
- package/src/manual-sync.ts +23 -12
- package/src/query.ts +111 -36
- package/src/serialization.ts +11 -4
package/src/manual-sync.ts
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
DuplicateKeyInBatchError,
|
|
4
4
|
SyncNotInitializedError,
|
|
5
5
|
UpdateOperationItemNotFoundError,
|
|
6
|
-
} from
|
|
7
|
-
import type { QueryClient } from
|
|
8
|
-
import type { ChangeMessage, Collection } from
|
|
6
|
+
} from './errors'
|
|
7
|
+
import type { QueryClient } from '@tanstack/query-core'
|
|
8
|
+
import type { ChangeMessage, Collection } from '@tanstack/db'
|
|
9
9
|
|
|
10
10
|
// Track active batch operations per context to prevent cross-collection contamination
|
|
11
11
|
const activeBatchContexts = new WeakMap<
|
|
@@ -38,6 +38,12 @@ export interface SyncContext<
|
|
|
38
38
|
begin: () => void
|
|
39
39
|
write: (message: Omit<ChangeMessage<TRow>, `key`>) => void
|
|
40
40
|
commit: () => void
|
|
41
|
+
/**
|
|
42
|
+
* Optional function to update the query cache with the latest synced data.
|
|
43
|
+
* Handles both direct array caches and wrapped response formats (when `select` is used).
|
|
44
|
+
* If not provided, falls back to directly setting the cache with the raw array.
|
|
45
|
+
*/
|
|
46
|
+
updateCacheData?: (items: Array<TRow>) => void
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
interface NormalizedOperation<
|
|
@@ -58,7 +64,7 @@ function normalizeOperations<
|
|
|
58
64
|
ops:
|
|
59
65
|
| SyncOperation<TRow, TKey, TInsertInput>
|
|
60
66
|
| Array<SyncOperation<TRow, TKey, TInsertInput>>,
|
|
61
|
-
ctx: SyncContext<TRow, TKey
|
|
67
|
+
ctx: SyncContext<TRow, TKey>,
|
|
62
68
|
): Array<NormalizedOperation<TRow, TKey>> {
|
|
63
69
|
const operations = Array.isArray(ops) ? ops : [ops]
|
|
64
70
|
const normalized: Array<NormalizedOperation<TRow, TKey>> = []
|
|
@@ -80,7 +86,7 @@ function normalizeOperations<
|
|
|
80
86
|
// For insert/upsert, validate and resolve the full item first
|
|
81
87
|
const resolved = ctx.collection.validateData(
|
|
82
88
|
item,
|
|
83
|
-
op.type === `upsert` ? `insert` : op.type
|
|
89
|
+
op.type === `upsert` ? `insert` : op.type,
|
|
84
90
|
)
|
|
85
91
|
key = ctx.getKey(resolved)
|
|
86
92
|
}
|
|
@@ -98,7 +104,7 @@ function validateOperations<
|
|
|
98
104
|
TKey extends string | number = string | number,
|
|
99
105
|
>(
|
|
100
106
|
operations: Array<NormalizedOperation<TRow, TKey>>,
|
|
101
|
-
ctx: SyncContext<TRow, TKey
|
|
107
|
+
ctx: SyncContext<TRow, TKey>,
|
|
102
108
|
): void {
|
|
103
109
|
const seenKeys = new Set<TKey>()
|
|
104
110
|
|
|
@@ -133,7 +139,7 @@ export function performWriteOperations<
|
|
|
133
139
|
operations:
|
|
134
140
|
| SyncOperation<TRow, TKey, TInsertInput>
|
|
135
141
|
| Array<SyncOperation<TRow, TKey, TInsertInput>>,
|
|
136
|
-
ctx: SyncContext<TRow, TKey
|
|
142
|
+
ctx: SyncContext<TRow, TKey>,
|
|
137
143
|
): void {
|
|
138
144
|
const normalized = normalizeOperations(operations, ctx)
|
|
139
145
|
validateOperations(normalized, ctx)
|
|
@@ -160,7 +166,7 @@ export function performWriteOperations<
|
|
|
160
166
|
const resolved = ctx.collection.validateData(
|
|
161
167
|
updatedItem,
|
|
162
168
|
`update`,
|
|
163
|
-
op.key
|
|
169
|
+
op.key,
|
|
164
170
|
)
|
|
165
171
|
ctx.write({
|
|
166
172
|
type: `update`,
|
|
@@ -183,7 +189,7 @@ export function performWriteOperations<
|
|
|
183
189
|
const resolved = ctx.collection.validateData(
|
|
184
190
|
op.data,
|
|
185
191
|
existsInSyncedStore ? `update` : `insert`,
|
|
186
|
-
op.key
|
|
192
|
+
op.key,
|
|
187
193
|
)
|
|
188
194
|
if (existsInSyncedStore) {
|
|
189
195
|
ctx.write({
|
|
@@ -205,7 +211,12 @@ export function performWriteOperations<
|
|
|
205
211
|
|
|
206
212
|
// Update query cache after successful commit
|
|
207
213
|
const updatedData = Array.from(ctx.collection._state.syncedData.values())
|
|
208
|
-
|
|
214
|
+
if (ctx.updateCacheData) {
|
|
215
|
+
ctx.updateCacheData(updatedData)
|
|
216
|
+
} else {
|
|
217
|
+
// Fallback: directly set the cache with raw array (for non-Query Collection consumers)
|
|
218
|
+
ctx.queryClient.setQueryData(ctx.queryKey, updatedData)
|
|
219
|
+
}
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
// Factory function to create write utils
|
|
@@ -300,7 +311,7 @@ export function createWriteUtils<
|
|
|
300
311
|
const existingBatch = activeBatchContexts.get(ctx)
|
|
301
312
|
if (existingBatch?.isActive) {
|
|
302
313
|
throw new Error(
|
|
303
|
-
`Cannot nest writeBatch calls. Complete the current batch before starting a new one
|
|
314
|
+
`Cannot nest writeBatch calls. Complete the current batch before starting a new one.`,
|
|
304
315
|
)
|
|
305
316
|
}
|
|
306
317
|
|
|
@@ -326,7 +337,7 @@ export function createWriteUtils<
|
|
|
326
337
|
typeof result.then === `function`
|
|
327
338
|
) {
|
|
328
339
|
throw new Error(
|
|
329
|
-
`writeBatch does not support async callbacks. The callback must be synchronous
|
|
340
|
+
`writeBatch does not support async callbacks. The callback must be synchronous.`,
|
|
330
341
|
)
|
|
331
342
|
}
|
|
332
343
|
|
package/src/query.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { QueryObserver, hashKey } from
|
|
2
|
-
import { deepEquals } from
|
|
1
|
+
import { QueryObserver, hashKey } from '@tanstack/query-core'
|
|
2
|
+
import { deepEquals } from '@tanstack/db'
|
|
3
3
|
import {
|
|
4
4
|
GetKeyRequiredError,
|
|
5
5
|
QueryClientRequiredError,
|
|
6
6
|
QueryFnRequiredError,
|
|
7
7
|
QueryKeyRequiredError,
|
|
8
|
-
} from
|
|
9
|
-
import { createWriteUtils } from
|
|
10
|
-
import { serializeLoadSubsetOptions } from
|
|
8
|
+
} from './errors'
|
|
9
|
+
import { createWriteUtils } from './manual-sync'
|
|
10
|
+
import { serializeLoadSubsetOptions } from './serialization'
|
|
11
11
|
import type {
|
|
12
12
|
BaseCollectionConfig,
|
|
13
13
|
ChangeMessage,
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
SyncConfig,
|
|
19
19
|
UpdateMutationFnParams,
|
|
20
20
|
UtilsRecord,
|
|
21
|
-
} from
|
|
21
|
+
} from '@tanstack/db'
|
|
22
22
|
import type {
|
|
23
23
|
FetchStatus,
|
|
24
24
|
QueryClient,
|
|
@@ -26,11 +26,11 @@ import type {
|
|
|
26
26
|
QueryKey,
|
|
27
27
|
QueryObserverOptions,
|
|
28
28
|
QueryObserverResult,
|
|
29
|
-
} from
|
|
30
|
-
import type { StandardSchemaV1 } from
|
|
29
|
+
} from '@tanstack/query-core'
|
|
30
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
31
31
|
|
|
32
32
|
// Re-export for external use
|
|
33
|
-
export type { SyncOperation } from
|
|
33
|
+
export type { SyncOperation } from './manual-sync'
|
|
34
34
|
|
|
35
35
|
// Schema output type inference helper (matches electric.ts pattern)
|
|
36
36
|
type InferSchemaOutput<T> = T extends StandardSchemaV1
|
|
@@ -60,7 +60,7 @@ type TQueryKeyBuilder<TQueryKey> = (opts: LoadSubsetOptions) => TQueryKey
|
|
|
60
60
|
export interface QueryCollectionConfig<
|
|
61
61
|
T extends object = object,
|
|
62
62
|
TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (
|
|
63
|
-
context: QueryFunctionContext<any
|
|
63
|
+
context: QueryFunctionContext<any>,
|
|
64
64
|
) => Promise<any>,
|
|
65
65
|
TError = unknown,
|
|
66
66
|
TQueryKey extends QueryKey = QueryKey,
|
|
@@ -72,7 +72,7 @@ export interface QueryCollectionConfig<
|
|
|
72
72
|
queryKey: TQueryKey | TQueryKeyBuilder<TQueryKey>
|
|
73
73
|
/** Function that fetches data from the server. Must return the complete collection state */
|
|
74
74
|
queryFn: TQueryFn extends (
|
|
75
|
-
context: QueryFunctionContext<TQueryKey
|
|
75
|
+
context: QueryFunctionContext<TQueryKey>,
|
|
76
76
|
) => Promise<Array<any>>
|
|
77
77
|
? (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>
|
|
78
78
|
: TQueryFn
|
|
@@ -232,7 +232,7 @@ class QueryCollectionUtilsImpl {
|
|
|
232
232
|
constructor(
|
|
233
233
|
state: QueryCollectionState,
|
|
234
234
|
refetch: RefetchFn,
|
|
235
|
-
writeUtils: ReturnType<typeof createWriteUtils
|
|
235
|
+
writeUtils: ReturnType<typeof createWriteUtils>,
|
|
236
236
|
) {
|
|
237
237
|
this.state = state
|
|
238
238
|
this.refetchFn = refetch
|
|
@@ -270,21 +270,21 @@ class QueryCollectionUtilsImpl {
|
|
|
270
270
|
public get isFetching() {
|
|
271
271
|
// check if any observer is fetching
|
|
272
272
|
return Array.from(this.state.observers.values()).some(
|
|
273
|
-
(observer) => observer.getCurrentResult().isFetching
|
|
273
|
+
(observer) => observer.getCurrentResult().isFetching,
|
|
274
274
|
)
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
public get isRefetching() {
|
|
278
278
|
// check if any observer is refetching
|
|
279
279
|
return Array.from(this.state.observers.values()).some(
|
|
280
|
-
(observer) => observer.getCurrentResult().isRefetching
|
|
280
|
+
(observer) => observer.getCurrentResult().isRefetching,
|
|
281
281
|
)
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
public get isLoading() {
|
|
285
285
|
// check if any observer is loading
|
|
286
286
|
return Array.from(this.state.observers.values()).some(
|
|
287
|
-
(observer) => observer.getCurrentResult().isLoading
|
|
287
|
+
(observer) => observer.getCurrentResult().isLoading,
|
|
288
288
|
)
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -293,14 +293,14 @@ class QueryCollectionUtilsImpl {
|
|
|
293
293
|
return Math.max(
|
|
294
294
|
0,
|
|
295
295
|
...Array.from(this.state.observers.values()).map(
|
|
296
|
-
(observer) => observer.getCurrentResult().dataUpdatedAt
|
|
297
|
-
)
|
|
296
|
+
(observer) => observer.getCurrentResult().dataUpdatedAt,
|
|
297
|
+
),
|
|
298
298
|
)
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
public get fetchStatus(): Array<FetchStatus> {
|
|
302
302
|
return Array.from(this.state.observers.values()).map(
|
|
303
|
-
(observer) => observer.getCurrentResult().fetchStatus
|
|
303
|
+
(observer) => observer.getCurrentResult().fetchStatus,
|
|
304
304
|
)
|
|
305
305
|
}
|
|
306
306
|
}
|
|
@@ -409,7 +409,7 @@ export function queryCollectionOptions<
|
|
|
409
409
|
> & {
|
|
410
410
|
schema: T
|
|
411
411
|
select: (data: TQueryData) => Array<InferSchemaInput<T>>
|
|
412
|
-
}
|
|
412
|
+
},
|
|
413
413
|
): CollectionConfig<
|
|
414
414
|
InferSchemaOutput<T>,
|
|
415
415
|
TKey,
|
|
@@ -429,7 +429,7 @@ export function queryCollectionOptions<
|
|
|
429
429
|
export function queryCollectionOptions<
|
|
430
430
|
T extends object,
|
|
431
431
|
TQueryFn extends (context: QueryFunctionContext<any>) => Promise<any> = (
|
|
432
|
-
context: QueryFunctionContext<any
|
|
432
|
+
context: QueryFunctionContext<any>,
|
|
433
433
|
) => Promise<any>,
|
|
434
434
|
TError = unknown,
|
|
435
435
|
TQueryKey extends QueryKey = QueryKey,
|
|
@@ -447,7 +447,7 @@ export function queryCollectionOptions<
|
|
|
447
447
|
> & {
|
|
448
448
|
schema?: never // prohibit schema
|
|
449
449
|
select: (data: TQueryData) => Array<T>
|
|
450
|
-
}
|
|
450
|
+
},
|
|
451
451
|
): CollectionConfig<
|
|
452
452
|
T,
|
|
453
453
|
TKey,
|
|
@@ -468,7 +468,7 @@ export function queryCollectionOptions<
|
|
|
468
468
|
config: QueryCollectionConfig<
|
|
469
469
|
InferSchemaOutput<T>,
|
|
470
470
|
(
|
|
471
|
-
context: QueryFunctionContext<any
|
|
471
|
+
context: QueryFunctionContext<any>,
|
|
472
472
|
) => Promise<Array<InferSchemaOutput<T>>>,
|
|
473
473
|
TError,
|
|
474
474
|
TQueryKey,
|
|
@@ -476,7 +476,7 @@ export function queryCollectionOptions<
|
|
|
476
476
|
T
|
|
477
477
|
> & {
|
|
478
478
|
schema: T
|
|
479
|
-
}
|
|
479
|
+
},
|
|
480
480
|
): CollectionConfig<
|
|
481
481
|
InferSchemaOutput<T>,
|
|
482
482
|
TKey,
|
|
@@ -507,7 +507,7 @@ export function queryCollectionOptions<
|
|
|
507
507
|
TKey
|
|
508
508
|
> & {
|
|
509
509
|
schema?: never // prohibit schema
|
|
510
|
-
}
|
|
510
|
+
},
|
|
511
511
|
): CollectionConfig<
|
|
512
512
|
T,
|
|
513
513
|
TKey,
|
|
@@ -519,7 +519,7 @@ export function queryCollectionOptions<
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
export function queryCollectionOptions(
|
|
522
|
-
config: QueryCollectionConfig<Record<string, unknown
|
|
522
|
+
config: QueryCollectionConfig<Record<string, unknown>>,
|
|
523
523
|
): CollectionConfig<
|
|
524
524
|
Record<string, unknown>,
|
|
525
525
|
string | number,
|
|
@@ -663,7 +663,7 @@ export function queryCollectionOptions(
|
|
|
663
663
|
|
|
664
664
|
const createQueryFromOpts = (
|
|
665
665
|
opts: LoadSubsetOptions = {},
|
|
666
|
-
queryFunction: typeof queryFn = queryFn
|
|
666
|
+
queryFunction: typeof queryFn = queryFn,
|
|
667
667
|
): true | Promise<void> => {
|
|
668
668
|
// Generate key using common function
|
|
669
669
|
const key = generateQueryKeyFromOptions(opts)
|
|
@@ -675,7 +675,7 @@ export function queryCollectionOptions(
|
|
|
675
675
|
// Increment reference count since another consumer is using this observer
|
|
676
676
|
queryRefCounts.set(
|
|
677
677
|
hashedQueryKey,
|
|
678
|
-
(queryRefCounts.get(hashedQueryKey) || 0) + 1
|
|
678
|
+
(queryRefCounts.get(hashedQueryKey) || 0) + 1,
|
|
679
679
|
)
|
|
680
680
|
|
|
681
681
|
// Get the current result and return based on its state
|
|
@@ -739,7 +739,7 @@ export function queryCollectionOptions(
|
|
|
739
739
|
// Increment reference count for this query
|
|
740
740
|
queryRefCounts.set(
|
|
741
741
|
hashedQueryKey,
|
|
742
|
-
(queryRefCounts.get(hashedQueryKey) || 0) + 1
|
|
742
|
+
(queryRefCounts.get(hashedQueryKey) || 0) + 1,
|
|
743
743
|
)
|
|
744
744
|
|
|
745
745
|
// Create a promise that resolves when the query result is first available
|
|
@@ -790,7 +790,7 @@ export function queryCollectionOptions(
|
|
|
790
790
|
}
|
|
791
791
|
|
|
792
792
|
const currentSyncedItems: Map<string | number, any> = new Map(
|
|
793
|
-
collection._state.syncedData.entries()
|
|
793
|
+
collection._state.syncedData.entries(),
|
|
794
794
|
)
|
|
795
795
|
const newItemsMap = new Map<string | number, any>()
|
|
796
796
|
newItemsArray.forEach((item) => {
|
|
@@ -833,7 +833,7 @@ export function queryCollectionOptions(
|
|
|
833
833
|
|
|
834
834
|
console.error(
|
|
835
835
|
`[QueryCollection] Error observing query ${String(queryKey)}:`,
|
|
836
|
-
result.error
|
|
836
|
+
result.error,
|
|
837
837
|
)
|
|
838
838
|
|
|
839
839
|
// Mark collection as ready even on error to avoid blocking apps
|
|
@@ -849,7 +849,7 @@ export function queryCollectionOptions(
|
|
|
849
849
|
|
|
850
850
|
const subscribeToQuery = (
|
|
851
851
|
observer: QueryObserver<Array<any>, any, Array<any>, Array<any>, any>,
|
|
852
|
-
hashedQueryKey: string
|
|
852
|
+
hashedQueryKey: string,
|
|
853
853
|
) => {
|
|
854
854
|
if (!isSubscribed(hashedQueryKey)) {
|
|
855
855
|
const cachedQueryKey = hashToQueryKey.get(hashedQueryKey)!
|
|
@@ -889,7 +889,7 @@ export function queryCollectionOptions(
|
|
|
889
889
|
} else if (subscriberCount === 0) {
|
|
890
890
|
unsubscribeFromQueries()
|
|
891
891
|
}
|
|
892
|
-
}
|
|
892
|
+
},
|
|
893
893
|
)
|
|
894
894
|
|
|
895
895
|
// If syncMode is eager, create the initial query without any predicates
|
|
@@ -989,7 +989,7 @@ export function queryCollectionOptions(
|
|
|
989
989
|
if (refcount > 0) {
|
|
990
990
|
console.warn(
|
|
991
991
|
`[cleanupQueryIfIdle] Invariant violation: refcount=${refcount} but no listeners. Cleaning up to prevent leak.`,
|
|
992
|
-
{ hashedQueryKey }
|
|
992
|
+
{ hashedQueryKey },
|
|
993
993
|
)
|
|
994
994
|
}
|
|
995
995
|
|
|
@@ -1040,7 +1040,7 @@ export function queryCollectionOptions(
|
|
|
1040
1040
|
allQueryKeys.map(async (qKey) => {
|
|
1041
1041
|
await queryClient.cancelQueries({ queryKey: qKey, exact: true })
|
|
1042
1042
|
queryClient.removeQueries({ queryKey: qKey, exact: true })
|
|
1043
|
-
})
|
|
1043
|
+
}),
|
|
1044
1044
|
)
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
@@ -1130,6 +1130,73 @@ export function queryCollectionOptions(
|
|
|
1130
1130
|
await Promise.all(refetchPromises)
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
|
+
/**
|
|
1134
|
+
* Updates the query cache with new items, handling both direct arrays
|
|
1135
|
+
* and wrapped response formats (when `select` is used).
|
|
1136
|
+
*/
|
|
1137
|
+
const updateCacheData = (items: Array<any>): void => {
|
|
1138
|
+
// Get the base query key (handle both static and function-based keys)
|
|
1139
|
+
const key =
|
|
1140
|
+
typeof queryKey === `function`
|
|
1141
|
+
? queryKey({})
|
|
1142
|
+
: (queryKey as unknown as QueryKey)
|
|
1143
|
+
|
|
1144
|
+
if (select) {
|
|
1145
|
+
// When `select` is used, the cache contains a wrapped response (e.g., { data: [...], meta: {...} })
|
|
1146
|
+
// We need to update the cache while preserving the wrapper structure
|
|
1147
|
+
queryClient.setQueryData(key, (oldData: any) => {
|
|
1148
|
+
if (!oldData || typeof oldData !== `object`) {
|
|
1149
|
+
// No existing cache or not an object - don't corrupt the cache
|
|
1150
|
+
return oldData
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (Array.isArray(oldData)) {
|
|
1154
|
+
// Cache is already a raw array (shouldn't happen with select, but handle it)
|
|
1155
|
+
return items
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Use the select function to identify which property contains the items array.
|
|
1159
|
+
// This is more robust than guessing based on property order.
|
|
1160
|
+
const selectedArray = select(oldData)
|
|
1161
|
+
|
|
1162
|
+
if (Array.isArray(selectedArray)) {
|
|
1163
|
+
// Find the property that matches the selected array by reference equality
|
|
1164
|
+
for (const propKey of Object.keys(oldData)) {
|
|
1165
|
+
if (oldData[propKey] === selectedArray) {
|
|
1166
|
+
// Found the exact property - create a shallow copy with updated items
|
|
1167
|
+
return { ...oldData, [propKey]: items }
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Fallback: check common property names used for data arrays
|
|
1173
|
+
if (Array.isArray(oldData.data)) {
|
|
1174
|
+
return { ...oldData, data: items }
|
|
1175
|
+
}
|
|
1176
|
+
if (Array.isArray(oldData.items)) {
|
|
1177
|
+
return { ...oldData, items: items }
|
|
1178
|
+
}
|
|
1179
|
+
if (Array.isArray(oldData.results)) {
|
|
1180
|
+
return { ...oldData, results: items }
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Last resort: find first array property
|
|
1184
|
+
for (const propKey of Object.keys(oldData)) {
|
|
1185
|
+
if (Array.isArray(oldData[propKey])) {
|
|
1186
|
+
return { ...oldData, [propKey]: items }
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Couldn't safely identify the array property - don't corrupt the cache
|
|
1191
|
+
// Return oldData unchanged to avoid breaking select
|
|
1192
|
+
return oldData
|
|
1193
|
+
})
|
|
1194
|
+
} else {
|
|
1195
|
+
// No select - cache contains raw array, just set it directly
|
|
1196
|
+
queryClient.setQueryData(key, items)
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1133
1200
|
// Create write context for manual write operations
|
|
1134
1201
|
let writeContext: {
|
|
1135
1202
|
collection: any
|
|
@@ -1139,21 +1206,29 @@ export function queryCollectionOptions(
|
|
|
1139
1206
|
begin: () => void
|
|
1140
1207
|
write: (message: Omit<ChangeMessage<any>, `key`>) => void
|
|
1141
1208
|
commit: () => void
|
|
1209
|
+
updateCacheData?: (items: Array<any>) => void
|
|
1142
1210
|
} | null = null
|
|
1143
1211
|
|
|
1144
1212
|
// Enhanced internalSync that captures write functions for manual use
|
|
1145
1213
|
const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {
|
|
1146
1214
|
const { begin, write, commit, collection } = params
|
|
1147
1215
|
|
|
1216
|
+
// Get the base query key for the context (handle both static and function-based keys)
|
|
1217
|
+
const contextQueryKey =
|
|
1218
|
+
typeof queryKey === `function`
|
|
1219
|
+
? (queryKey({}) as unknown as Array<unknown>)
|
|
1220
|
+
: (queryKey as unknown as Array<unknown>)
|
|
1221
|
+
|
|
1148
1222
|
// Store references for manual write operations
|
|
1149
1223
|
writeContext = {
|
|
1150
1224
|
collection,
|
|
1151
1225
|
queryClient,
|
|
1152
|
-
queryKey:
|
|
1226
|
+
queryKey: contextQueryKey,
|
|
1153
1227
|
getKey: getKey as (item: any) => string | number,
|
|
1154
1228
|
begin,
|
|
1155
1229
|
write,
|
|
1156
1230
|
commit,
|
|
1231
|
+
updateCacheData,
|
|
1157
1232
|
}
|
|
1158
1233
|
|
|
1159
1234
|
// Call the original internalSync logic
|
|
@@ -1162,7 +1237,7 @@ export function queryCollectionOptions(
|
|
|
1162
1237
|
|
|
1163
1238
|
// Create write utils using the manual-sync module
|
|
1164
1239
|
const writeUtils = createWriteUtils<any, string | number, any>(
|
|
1165
|
-
() => writeContext
|
|
1240
|
+
() => writeContext,
|
|
1166
1241
|
)
|
|
1167
1242
|
|
|
1168
1243
|
// 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
|
|