@tanstack/query-db-collection 0.1.0 → 0.1.2
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 +65 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +27 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/manual-sync.cjs +147 -0
- package/dist/cjs/manual-sync.cjs.map +1 -0
- package/dist/cjs/manual-sync.d.cts +32 -0
- package/dist/cjs/query.cjs +21 -2
- package/dist/cjs/query.cjs.map +1 -1
- package/dist/cjs/query.d.cts +13 -3
- package/dist/esm/errors.d.ts +27 -0
- package/dist/esm/errors.js +66 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +10 -1
- package/dist/esm/manual-sync.d.ts +32 -0
- package/dist/esm/manual-sync.js +147 -0
- package/dist/esm/manual-sync.js.map +1 -0
- package/dist/esm/query.d.ts +13 -3
- package/dist/esm/query.js +21 -2
- package/dist/esm/query.js.map +1 -1
- package/package.json +3 -2
- package/src/errors.ts +65 -0
- package/src/index.ts +1 -0
- package/src/manual-sync.ts +236 -0
- package/src/query.ts +63 -3
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeleteOperationItemNotFoundError,
|
|
3
|
+
DuplicateKeyInBatchError,
|
|
4
|
+
SyncNotInitializedError,
|
|
5
|
+
UpdateOperationItemNotFoundError,
|
|
6
|
+
} from "./errors"
|
|
7
|
+
import type { QueryClient } from "@tanstack/query-core"
|
|
8
|
+
import type { ChangeMessage, Collection } from "@tanstack/db"
|
|
9
|
+
|
|
10
|
+
// Types for sync operations
|
|
11
|
+
export type SyncOperation<
|
|
12
|
+
TRow extends object,
|
|
13
|
+
TKey extends string | number = string | number,
|
|
14
|
+
TInsertInput extends object = TRow,
|
|
15
|
+
> =
|
|
16
|
+
| { type: `insert`; data: TInsertInput | Array<TInsertInput> }
|
|
17
|
+
| { type: `update`; data: Partial<TRow> | Array<Partial<TRow>> }
|
|
18
|
+
| { type: `delete`; key: TKey | Array<TKey> }
|
|
19
|
+
| { type: `upsert`; data: Partial<TRow> | Array<Partial<TRow>> }
|
|
20
|
+
|
|
21
|
+
export interface SyncContext<
|
|
22
|
+
TRow extends object,
|
|
23
|
+
TKey extends string | number = string | number,
|
|
24
|
+
> {
|
|
25
|
+
collection: Collection<TRow>
|
|
26
|
+
queryClient: QueryClient
|
|
27
|
+
queryKey: Array<unknown>
|
|
28
|
+
getKey: (item: TRow) => TKey
|
|
29
|
+
begin: () => void
|
|
30
|
+
write: (message: Omit<ChangeMessage<TRow>, `key`>) => void
|
|
31
|
+
commit: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface NormalizedOperation<
|
|
35
|
+
TRow extends object,
|
|
36
|
+
TKey extends string | number = string | number,
|
|
37
|
+
> {
|
|
38
|
+
type: `insert` | `update` | `delete` | `upsert`
|
|
39
|
+
key: TKey
|
|
40
|
+
data?: TRow | Partial<TRow>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Normalize operations into a consistent format
|
|
44
|
+
function normalizeOperations<
|
|
45
|
+
TRow extends object,
|
|
46
|
+
TKey extends string | number = string | number,
|
|
47
|
+
TInsertInput extends object = TRow,
|
|
48
|
+
>(
|
|
49
|
+
ops:
|
|
50
|
+
| SyncOperation<TRow, TKey, TInsertInput>
|
|
51
|
+
| Array<SyncOperation<TRow, TKey, TInsertInput>>,
|
|
52
|
+
ctx: SyncContext<TRow, TKey>
|
|
53
|
+
): Array<NormalizedOperation<TRow, TKey>> {
|
|
54
|
+
const operations = Array.isArray(ops) ? ops : [ops]
|
|
55
|
+
const normalized: Array<NormalizedOperation<TRow, TKey>> = []
|
|
56
|
+
|
|
57
|
+
for (const op of operations) {
|
|
58
|
+
if (op.type === `delete`) {
|
|
59
|
+
const keys = Array.isArray(op.key) ? op.key : [op.key]
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
normalized.push({ type: `delete`, key })
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
const items = Array.isArray(op.data) ? op.data : [op.data]
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
let key: TKey
|
|
67
|
+
if (op.type === `update`) {
|
|
68
|
+
// For updates, we need to get the key from the partial data
|
|
69
|
+
key = ctx.getKey(item as TRow)
|
|
70
|
+
} else {
|
|
71
|
+
// For insert/upsert, validate and resolve the full item first
|
|
72
|
+
const resolved = ctx.collection.validateData(
|
|
73
|
+
item,
|
|
74
|
+
op.type === `upsert` ? `insert` : op.type
|
|
75
|
+
)
|
|
76
|
+
key = ctx.getKey(resolved)
|
|
77
|
+
}
|
|
78
|
+
normalized.push({ type: op.type, key, data: item })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return normalized
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validate operations before executing
|
|
87
|
+
function validateOperations<
|
|
88
|
+
TRow extends object,
|
|
89
|
+
TKey extends string | number = string | number,
|
|
90
|
+
>(
|
|
91
|
+
operations: Array<NormalizedOperation<TRow, TKey>>,
|
|
92
|
+
ctx: SyncContext<TRow, TKey>
|
|
93
|
+
): void {
|
|
94
|
+
const seenKeys = new Set<TKey>()
|
|
95
|
+
|
|
96
|
+
for (const op of operations) {
|
|
97
|
+
// Check for duplicate keys within the batch
|
|
98
|
+
if (seenKeys.has(op.key)) {
|
|
99
|
+
throw new DuplicateKeyInBatchError(op.key)
|
|
100
|
+
}
|
|
101
|
+
seenKeys.add(op.key)
|
|
102
|
+
|
|
103
|
+
// Validate operation-specific requirements
|
|
104
|
+
if (op.type === `update`) {
|
|
105
|
+
if (!ctx.collection.has(op.key)) {
|
|
106
|
+
throw new UpdateOperationItemNotFoundError(op.key)
|
|
107
|
+
}
|
|
108
|
+
} else if (op.type === `delete`) {
|
|
109
|
+
if (!ctx.collection.has(op.key)) {
|
|
110
|
+
throw new DeleteOperationItemNotFoundError(op.key)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Execute a batch of operations
|
|
117
|
+
export function performWriteOperations<
|
|
118
|
+
TRow extends object,
|
|
119
|
+
TKey extends string | number = string | number,
|
|
120
|
+
TInsertInput extends object = TRow,
|
|
121
|
+
>(
|
|
122
|
+
operations:
|
|
123
|
+
| SyncOperation<TRow, TKey, TInsertInput>
|
|
124
|
+
| Array<SyncOperation<TRow, TKey, TInsertInput>>,
|
|
125
|
+
ctx: SyncContext<TRow, TKey>
|
|
126
|
+
): void {
|
|
127
|
+
const normalized = normalizeOperations(operations, ctx)
|
|
128
|
+
validateOperations(normalized, ctx)
|
|
129
|
+
|
|
130
|
+
ctx.begin()
|
|
131
|
+
|
|
132
|
+
for (const op of normalized) {
|
|
133
|
+
switch (op.type) {
|
|
134
|
+
case `insert`: {
|
|
135
|
+
const resolved = ctx.collection.validateData(op.data, `insert`)
|
|
136
|
+
ctx.write({
|
|
137
|
+
type: `insert`,
|
|
138
|
+
value: resolved,
|
|
139
|
+
})
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
case `update`: {
|
|
143
|
+
const currentItem = ctx.collection.get(op.key)!
|
|
144
|
+
const updatedItem = {
|
|
145
|
+
...currentItem,
|
|
146
|
+
...op.data,
|
|
147
|
+
}
|
|
148
|
+
const resolved = ctx.collection.validateData(
|
|
149
|
+
updatedItem,
|
|
150
|
+
`update`,
|
|
151
|
+
op.key
|
|
152
|
+
)
|
|
153
|
+
ctx.write({
|
|
154
|
+
type: `update`,
|
|
155
|
+
value: resolved,
|
|
156
|
+
})
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
case `delete`: {
|
|
160
|
+
const currentItem = ctx.collection.get(op.key)!
|
|
161
|
+
ctx.write({
|
|
162
|
+
type: `delete`,
|
|
163
|
+
value: currentItem,
|
|
164
|
+
})
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
case `upsert`: {
|
|
168
|
+
const resolved = ctx.collection.validateData(
|
|
169
|
+
op.data,
|
|
170
|
+
ctx.collection.has(op.key) ? `update` : `insert`,
|
|
171
|
+
op.key
|
|
172
|
+
)
|
|
173
|
+
if (ctx.collection.has(op.key)) {
|
|
174
|
+
ctx.write({
|
|
175
|
+
type: `update`,
|
|
176
|
+
value: resolved,
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
ctx.write({
|
|
180
|
+
type: `insert`,
|
|
181
|
+
value: resolved,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
ctx.commit()
|
|
190
|
+
|
|
191
|
+
// Update query cache after successful commit
|
|
192
|
+
const updatedData = ctx.collection.toArray
|
|
193
|
+
ctx.queryClient.setQueryData(ctx.queryKey, updatedData)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Factory function to create write utils
|
|
197
|
+
export function createWriteUtils<
|
|
198
|
+
TRow extends object,
|
|
199
|
+
TKey extends string | number = string | number,
|
|
200
|
+
TInsertInput extends object = TRow,
|
|
201
|
+
>(getContext: () => SyncContext<TRow, TKey> | null) {
|
|
202
|
+
function ensureContext(): SyncContext<TRow, TKey> {
|
|
203
|
+
const context = getContext()
|
|
204
|
+
if (!context) {
|
|
205
|
+
throw new SyncNotInitializedError()
|
|
206
|
+
}
|
|
207
|
+
return context
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
writeInsert(data: TInsertInput | Array<TInsertInput>) {
|
|
212
|
+
const ctx = ensureContext()
|
|
213
|
+
performWriteOperations({ type: `insert`, data }, ctx)
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
writeUpdate(data: Partial<TRow> | Array<Partial<TRow>>) {
|
|
217
|
+
const ctx = ensureContext()
|
|
218
|
+
performWriteOperations({ type: `update`, data }, ctx)
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
writeDelete(key: TKey | Array<TKey>) {
|
|
222
|
+
const ctx = ensureContext()
|
|
223
|
+
performWriteOperations({ type: `delete`, key }, ctx)
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
writeUpsert(data: Partial<TRow> | Array<Partial<TRow>>) {
|
|
227
|
+
const ctx = ensureContext()
|
|
228
|
+
performWriteOperations({ type: `upsert`, data }, ctx)
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
writeBatch(operations: Array<SyncOperation<TRow, TKey, TInsertInput>>) {
|
|
232
|
+
const ctx = ensureContext()
|
|
233
|
+
performWriteOperations(operations, ctx)
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
}
|
package/src/query.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
QueryFnRequiredError,
|
|
6
6
|
QueryKeyRequiredError,
|
|
7
7
|
} from "./errors"
|
|
8
|
+
import { createWriteUtils } from "./manual-sync"
|
|
9
|
+
import type { SyncOperation } from "./manual-sync"
|
|
8
10
|
import type {
|
|
9
11
|
QueryClient,
|
|
10
12
|
QueryFunctionContext,
|
|
@@ -12,6 +14,7 @@ import type {
|
|
|
12
14
|
QueryObserverOptions,
|
|
13
15
|
} from "@tanstack/query-core"
|
|
14
16
|
import type {
|
|
17
|
+
ChangeMessage,
|
|
15
18
|
CollectionConfig,
|
|
16
19
|
DeleteMutationFn,
|
|
17
20
|
DeleteMutationFnParams,
|
|
@@ -23,6 +26,9 @@ import type {
|
|
|
23
26
|
UtilsRecord,
|
|
24
27
|
} from "@tanstack/db"
|
|
25
28
|
|
|
29
|
+
// Re-export for external use
|
|
30
|
+
export type { SyncOperation } from "./manual-sync"
|
|
31
|
+
|
|
26
32
|
export interface QueryCollectionConfig<
|
|
27
33
|
TItem extends object,
|
|
28
34
|
TError = unknown,
|
|
@@ -222,8 +228,22 @@ export type RefetchFn = () => Promise<void>
|
|
|
222
228
|
/**
|
|
223
229
|
* Query collection utilities type
|
|
224
230
|
*/
|
|
225
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Write operation types for batch operations
|
|
233
|
+
*/
|
|
234
|
+
export interface QueryCollectionUtils<
|
|
235
|
+
TItem extends object = Record<string, unknown>,
|
|
236
|
+
TKey extends string | number = string | number,
|
|
237
|
+
TInsertInput extends object = TItem,
|
|
238
|
+
> extends UtilsRecord {
|
|
226
239
|
refetch: RefetchFn
|
|
240
|
+
writeInsert: (data: TInsertInput | Array<TInsertInput>) => void
|
|
241
|
+
writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void
|
|
242
|
+
writeDelete: (keys: TKey | Array<TKey>) => void
|
|
243
|
+
writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void
|
|
244
|
+
writeBatch: (
|
|
245
|
+
operations: Array<SyncOperation<TItem, TKey, TInsertInput>>
|
|
246
|
+
) => void
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
/**
|
|
@@ -236,9 +256,13 @@ export function queryCollectionOptions<
|
|
|
236
256
|
TItem extends object,
|
|
237
257
|
TError = unknown,
|
|
238
258
|
TQueryKey extends QueryKey = QueryKey,
|
|
259
|
+
TKey extends string | number = string | number,
|
|
260
|
+
TInsertInput extends object = TItem,
|
|
239
261
|
>(
|
|
240
262
|
config: QueryCollectionConfig<TItem, TError, TQueryKey>
|
|
241
|
-
): CollectionConfig<TItem> & {
|
|
263
|
+
): CollectionConfig<TItem> & {
|
|
264
|
+
utils: QueryCollectionUtils<TItem, TKey, TInsertInput>
|
|
265
|
+
} {
|
|
242
266
|
const {
|
|
243
267
|
queryKey,
|
|
244
268
|
queryFn,
|
|
@@ -407,6 +431,41 @@ export function queryCollectionOptions<
|
|
|
407
431
|
})
|
|
408
432
|
}
|
|
409
433
|
|
|
434
|
+
// Create write context for manual write operations
|
|
435
|
+
let writeContext: {
|
|
436
|
+
collection: any
|
|
437
|
+
queryClient: QueryClient
|
|
438
|
+
queryKey: Array<unknown>
|
|
439
|
+
getKey: (item: TItem) => TKey
|
|
440
|
+
begin: () => void
|
|
441
|
+
write: (message: Omit<ChangeMessage<TItem>, `key`>) => void
|
|
442
|
+
commit: () => void
|
|
443
|
+
} | null = null
|
|
444
|
+
|
|
445
|
+
// Enhanced internalSync that captures write functions for manual use
|
|
446
|
+
const enhancedInternalSync: SyncConfig<TItem>[`sync`] = (params) => {
|
|
447
|
+
const { begin, write, commit, collection } = params
|
|
448
|
+
|
|
449
|
+
// Store references for manual write operations
|
|
450
|
+
writeContext = {
|
|
451
|
+
collection,
|
|
452
|
+
queryClient,
|
|
453
|
+
queryKey: queryKey as unknown as Array<unknown>,
|
|
454
|
+
getKey: getKey as (item: TItem) => TKey,
|
|
455
|
+
begin,
|
|
456
|
+
write,
|
|
457
|
+
commit,
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Call the original internalSync logic
|
|
461
|
+
return internalSync(params)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Create write utils using the manual-sync module
|
|
465
|
+
const writeUtils = createWriteUtils<TItem, TKey, TInsertInput>(
|
|
466
|
+
() => writeContext
|
|
467
|
+
)
|
|
468
|
+
|
|
410
469
|
// Create wrapper handlers for direct persistence operations that handle refetching
|
|
411
470
|
const wrappedOnInsert = onInsert
|
|
412
471
|
? async (params: InsertMutationFnParams<TItem>) => {
|
|
@@ -453,12 +512,13 @@ export function queryCollectionOptions<
|
|
|
453
512
|
return {
|
|
454
513
|
...baseCollectionConfig,
|
|
455
514
|
getKey,
|
|
456
|
-
sync: { sync:
|
|
515
|
+
sync: { sync: enhancedInternalSync },
|
|
457
516
|
onInsert: wrappedOnInsert,
|
|
458
517
|
onUpdate: wrappedOnUpdate,
|
|
459
518
|
onDelete: wrappedOnDelete,
|
|
460
519
|
utils: {
|
|
461
520
|
refetch,
|
|
521
|
+
...writeUtils,
|
|
462
522
|
},
|
|
463
523
|
}
|
|
464
524
|
}
|