@tanstack/db 0.0.4 → 0.0.5
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/collection.cjs +113 -94
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +38 -11
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +87 -248
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +5 -5
- package/dist/cjs/query/compiled-query.cjs +23 -14
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +3 -1
- package/dist/cjs/query/evaluators.cjs +20 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +3 -2
- package/dist/cjs/query/extractors.cjs +20 -20
- package/dist/cjs/query/extractors.cjs.map +1 -1
- package/dist/cjs/query/extractors.d.cts +3 -3
- package/dist/cjs/query/group-by.cjs +12 -15
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +7 -7
- package/dist/cjs/query/joins.cjs +41 -55
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +3 -3
- package/dist/cjs/query/order-by.cjs +37 -84
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/order-by.d.cts +2 -2
- package/dist/cjs/query/pipeline-compiler.cjs +13 -18
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
- package/dist/cjs/query/query-builder.cjs +0 -12
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +4 -8
- package/dist/cjs/query/schema.d.cts +1 -6
- package/dist/cjs/query/select.cjs +35 -24
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/query/select.d.cts +2 -2
- package/dist/cjs/query/types.d.cts +1 -0
- package/dist/cjs/transactions.cjs +17 -8
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +41 -7
- package/dist/esm/collection.d.ts +38 -11
- package/dist/esm/collection.js +113 -94
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/proxy.d.ts +5 -5
- package/dist/esm/proxy.js +87 -248
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +3 -1
- package/dist/esm/query/compiled-query.js +23 -14
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/evaluators.d.ts +3 -2
- package/dist/esm/query/evaluators.js +21 -21
- package/dist/esm/query/evaluators.js.map +1 -1
- package/dist/esm/query/extractors.d.ts +3 -3
- package/dist/esm/query/extractors.js +20 -20
- package/dist/esm/query/extractors.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +7 -7
- package/dist/esm/query/group-by.js +14 -17
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +3 -3
- package/dist/esm/query/joins.js +42 -56
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.d.ts +2 -2
- package/dist/esm/query/order-by.js +39 -86
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +2 -1
- package/dist/esm/query/pipeline-compiler.js +14 -19
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/query-builder.d.ts +4 -8
- package/dist/esm/query/query-builder.js +0 -12
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +1 -6
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +36 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +17 -8
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +41 -7
- package/package.json +2 -2
- package/src/collection.ts +174 -121
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +22 -21
- package/src/query/extractors.ts +24 -21
- package/src/query/group-by.ts +24 -22
- package/src/query/joins.ts +88 -75
- package/src/query/order-by.ts +56 -106
- package/src/query/pipeline-compiler.ts +34 -37
- package/src/query/query-builder.ts +9 -23
- package/src/query/schema.ts +1 -10
- package/src/query/select.ts +44 -32
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +22 -13
- package/src/types.ts +48 -7
- package/dist/cjs/query/key-by.cjs +0 -43
- package/dist/cjs/query/key-by.cjs.map +0 -1
- package/dist/cjs/query/key-by.d.cts +0 -3
- package/dist/esm/query/key-by.d.ts +0 -3
- package/dist/esm/query/key-by.js +0 -43
- package/dist/esm/query/key-by.js.map +0 -1
- package/src/query/key-by.ts +0 -61
package/src/collection.ts
CHANGED
|
@@ -28,6 +28,19 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
|
28
28
|
operations: Array<OptimisticChangeMessage<T>>
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new Collection instance with the given configuration
|
|
33
|
+
*
|
|
34
|
+
* @template T - The type of items in the collection
|
|
35
|
+
* @param config - Configuration for the collection, including id and sync
|
|
36
|
+
* @returns A new Collection instance
|
|
37
|
+
*/
|
|
38
|
+
export function createCollection<T extends object = Record<string, unknown>>(
|
|
39
|
+
config: CollectionConfig<T>
|
|
40
|
+
): Collection<T> {
|
|
41
|
+
return new Collection<T>(config)
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/**
|
|
32
45
|
* Preloads a collection with the given configuration
|
|
33
46
|
* Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
|
|
@@ -57,6 +70,10 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
|
57
70
|
export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
58
71
|
config: CollectionConfig<T>
|
|
59
72
|
): Promise<Collection<T>> {
|
|
73
|
+
if (!config.id) {
|
|
74
|
+
throw new Error(`The id property is required for preloadCollection`)
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
// If the collection is already fully loaded, return a resolved promise
|
|
61
78
|
if (
|
|
62
79
|
collectionsStore.state.has(config.id) &&
|
|
@@ -76,10 +93,14 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
76
93
|
if (!collectionsStore.state.has(config.id)) {
|
|
77
94
|
collectionsStore.setState((prev) => {
|
|
78
95
|
const next = new Map(prev)
|
|
96
|
+
if (!config.id) {
|
|
97
|
+
throw new Error(`The id property is required for preloadCollection`)
|
|
98
|
+
}
|
|
79
99
|
next.set(
|
|
80
100
|
config.id,
|
|
81
101
|
new Collection<T>({
|
|
82
102
|
id: config.id,
|
|
103
|
+
getId: config.getId,
|
|
83
104
|
sync: config.sync,
|
|
84
105
|
schema: config.schema,
|
|
85
106
|
})
|
|
@@ -100,6 +121,9 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
100
121
|
|
|
101
122
|
// Register a one-time listener for the first commit
|
|
102
123
|
collection.onFirstCommit(() => {
|
|
124
|
+
if (!config.id) {
|
|
125
|
+
throw new Error(`The id property is required for preloadCollection`)
|
|
126
|
+
}
|
|
103
127
|
if (loadingCollections.has(config.id)) {
|
|
104
128
|
loadingCollections.delete(config.id)
|
|
105
129
|
resolveFirstCommit()
|
|
@@ -157,9 +181,6 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
157
181
|
public config: CollectionConfig<T>
|
|
158
182
|
private hasReceivedFirstCommit = false
|
|
159
183
|
|
|
160
|
-
// WeakMap to associate objects with their keys
|
|
161
|
-
public objectKeyMap = new WeakMap<object, string>()
|
|
162
|
-
|
|
163
184
|
// Array to store one-time commit listeners
|
|
164
185
|
private onFirstCommitCallbacks: Array<() => void> = []
|
|
165
186
|
|
|
@@ -172,7 +193,7 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
172
193
|
this.onFirstCommitCallbacks.push(callback)
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
public id =
|
|
196
|
+
public id = ``
|
|
176
197
|
|
|
177
198
|
/**
|
|
178
199
|
* Creates a new Collection instance
|
|
@@ -180,8 +201,19 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
180
201
|
* @param config - Configuration object for the collection
|
|
181
202
|
* @throws Error if sync config is missing
|
|
182
203
|
*/
|
|
183
|
-
constructor(config
|
|
184
|
-
|
|
204
|
+
constructor(config: CollectionConfig<T>) {
|
|
205
|
+
// eslint-disable-next-line
|
|
206
|
+
if (!config) {
|
|
207
|
+
throw new Error(`Collection requires a config`)
|
|
208
|
+
}
|
|
209
|
+
if (config.id) {
|
|
210
|
+
this.id = config.id
|
|
211
|
+
} else {
|
|
212
|
+
this.id = crypto.randomUUID()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// eslint-disable-next-line
|
|
216
|
+
if (!config.sync) {
|
|
185
217
|
throw new Error(`Collection requires a sync config`)
|
|
186
218
|
}
|
|
187
219
|
|
|
@@ -232,11 +264,9 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
232
264
|
this.derivedState = new Derived({
|
|
233
265
|
fn: ({ currDepVals: [syncedData, operations] }) => {
|
|
234
266
|
const combined = new Map<string, T>(syncedData)
|
|
235
|
-
const optimisticKeys = new Set<string>()
|
|
236
267
|
|
|
237
268
|
// Apply the optimistic operations on top of the synced state.
|
|
238
269
|
for (const operation of operations) {
|
|
239
|
-
optimisticKeys.add(operation.key)
|
|
240
270
|
if (operation.isActive) {
|
|
241
271
|
switch (operation.type) {
|
|
242
272
|
case `insert`:
|
|
@@ -252,13 +282,6 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
252
282
|
}
|
|
253
283
|
}
|
|
254
284
|
|
|
255
|
-
// Update object => key mappings
|
|
256
|
-
optimisticKeys.forEach((key) => {
|
|
257
|
-
if (combined.has(key)) {
|
|
258
|
-
this.objectKeyMap.set(combined.get(key)!, key)
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
|
|
262
285
|
return combined
|
|
263
286
|
},
|
|
264
287
|
deps: [this.syncedData, this.optimisticOperations],
|
|
@@ -353,7 +376,7 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
353
376
|
operations: [],
|
|
354
377
|
})
|
|
355
378
|
},
|
|
356
|
-
write: (
|
|
379
|
+
write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {
|
|
357
380
|
const pendingTransaction =
|
|
358
381
|
this.pendingSyncedTransactions[
|
|
359
382
|
this.pendingSyncedTransactions.length - 1
|
|
@@ -366,6 +389,30 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
366
389
|
`The pending sync transaction is already committed, you can't still write to it.`
|
|
367
390
|
)
|
|
368
391
|
}
|
|
392
|
+
const key = this.generateObjectKey(
|
|
393
|
+
this.config.getId(messageWithoutKey.value),
|
|
394
|
+
messageWithoutKey.value
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
// Check if an item with this ID already exists when inserting
|
|
398
|
+
if (messageWithoutKey.type === `insert`) {
|
|
399
|
+
if (
|
|
400
|
+
this.syncedData.state.has(key) &&
|
|
401
|
+
!pendingTransaction.operations.some(
|
|
402
|
+
(op) => op.key === key && op.type === `delete`
|
|
403
|
+
)
|
|
404
|
+
) {
|
|
405
|
+
const id = this.config.getId(messageWithoutKey.value)
|
|
406
|
+
throw new Error(
|
|
407
|
+
`Cannot insert document with ID "${id}" from sync because it already exists in the collection "${this.id}"`
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const message: ChangeMessage<T> = {
|
|
413
|
+
...messageWithoutKey,
|
|
414
|
+
key,
|
|
415
|
+
}
|
|
369
416
|
pendingTransaction.operations.push(message)
|
|
370
417
|
},
|
|
371
418
|
commit: () => {
|
|
@@ -399,11 +446,9 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
399
446
|
({ state }) => state === `persisting`
|
|
400
447
|
)
|
|
401
448
|
) {
|
|
402
|
-
const keys = new Set<string>()
|
|
403
449
|
batch(() => {
|
|
404
450
|
for (const transaction of this.pendingSyncedTransactions) {
|
|
405
451
|
for (const operation of transaction.operations) {
|
|
406
|
-
keys.add(operation.key)
|
|
407
452
|
this.syncedKeys.add(operation.key)
|
|
408
453
|
this.syncedMetadata.setState((prevData) => {
|
|
409
454
|
switch (operation.type) {
|
|
@@ -443,13 +488,6 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
443
488
|
}
|
|
444
489
|
})
|
|
445
490
|
|
|
446
|
-
keys.forEach((key) => {
|
|
447
|
-
const curValue = this.state.get(key)
|
|
448
|
-
if (curValue) {
|
|
449
|
-
this.objectKeyMap.set(curValue, key)
|
|
450
|
-
}
|
|
451
|
-
})
|
|
452
|
-
|
|
453
491
|
this.pendingSyncedTransactions = []
|
|
454
492
|
|
|
455
493
|
// Call any registered one-time commit listeners
|
|
@@ -473,6 +511,29 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
473
511
|
)
|
|
474
512
|
}
|
|
475
513
|
|
|
514
|
+
private getKeyFromId(id: unknown): string {
|
|
515
|
+
if (typeof id === `undefined`) {
|
|
516
|
+
throw new Error(`id is undefined`)
|
|
517
|
+
}
|
|
518
|
+
if (typeof id === `string` && id.startsWith(`KEY::`)) {
|
|
519
|
+
return id
|
|
520
|
+
} else {
|
|
521
|
+
// if it's not a string, then it's some other
|
|
522
|
+
// primitive type and needs turned into a key.
|
|
523
|
+
return this.generateObjectKey(id, null)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
public generateObjectKey(id: any, item: any): string {
|
|
528
|
+
if (typeof id === `undefined`) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
`An object was created without a defined id: ${JSON.stringify(item)}`
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return `KEY::${this.id}/${id}`
|
|
535
|
+
}
|
|
536
|
+
|
|
476
537
|
private validateData(
|
|
477
538
|
data: unknown,
|
|
478
539
|
type: `insert` | `update`,
|
|
@@ -539,17 +600,6 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
539
600
|
return result.value as T
|
|
540
601
|
}
|
|
541
602
|
|
|
542
|
-
private generateKey(data: unknown): string {
|
|
543
|
-
const str = JSON.stringify(data)
|
|
544
|
-
let h = 0
|
|
545
|
-
|
|
546
|
-
for (let i = 0; i < str.length; i++) {
|
|
547
|
-
h = (Math.imul(31, h) + str.charCodeAt(i)) | 0
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
return `${this.id}/${Math.abs(h).toString(36)}`
|
|
551
|
-
}
|
|
552
|
-
|
|
553
603
|
/**
|
|
554
604
|
* Inserts one or more items into the collection
|
|
555
605
|
* @param items - Single item or array of items to insert
|
|
@@ -579,18 +629,9 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
579
629
|
const mutations: Array<PendingMutation<T>> = []
|
|
580
630
|
|
|
581
631
|
// Handle keys - convert to array if string, or generate if not provided
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
// If keys are provided, ensure we have the right number or allow sparse array
|
|
586
|
-
if (Array.isArray(config.key) && configKeys.length > items.length) {
|
|
587
|
-
throw new Error(`More keys provided than items to insert`)
|
|
588
|
-
}
|
|
589
|
-
keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]))
|
|
590
|
-
} else {
|
|
591
|
-
// No keys provided, generate for all items
|
|
592
|
-
keys = items.map((item) => this.generateKey(item))
|
|
593
|
-
}
|
|
632
|
+
const keys: Array<unknown> = items.map((item) =>
|
|
633
|
+
this.generateObjectKey(this.config.getId(item), item)
|
|
634
|
+
)
|
|
594
635
|
|
|
595
636
|
// Create mutations for each item
|
|
596
637
|
items.forEach((item, index) => {
|
|
@@ -598,6 +639,12 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
598
639
|
const validatedData = this.validateData(item, `insert`)
|
|
599
640
|
const key = keys[index]!
|
|
600
641
|
|
|
642
|
+
// Check if an item with this ID already exists in the collection
|
|
643
|
+
const id = this.config.getId(item)
|
|
644
|
+
if (this.state.has(this.getKeyFromId(id))) {
|
|
645
|
+
throw `Cannot insert document with ID "${id}" because it already exists in the collection`
|
|
646
|
+
}
|
|
647
|
+
|
|
601
648
|
const mutation: PendingMutation<T> = {
|
|
602
649
|
mutationId: crypto.randomUUID(),
|
|
603
650
|
original: {},
|
|
@@ -645,24 +692,43 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
645
692
|
* update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
|
|
646
693
|
*/
|
|
647
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Updates one or more items in the collection using a callback function
|
|
697
|
+
* @param ids - Single ID or array of IDs to update
|
|
698
|
+
* @param configOrCallback - Either update configuration or update callback
|
|
699
|
+
* @param maybeCallback - Update callback if config was provided
|
|
700
|
+
* @returns A Transaction object representing the update operation(s)
|
|
701
|
+
* @throws {SchemaValidationError} If the updated data fails schema validation
|
|
702
|
+
* @example
|
|
703
|
+
* // Update a single item
|
|
704
|
+
* update("todo-1", (draft) => { draft.completed = true })
|
|
705
|
+
*
|
|
706
|
+
* // Update multiple items
|
|
707
|
+
* update(["todo-1", "todo-2"], (drafts) => {
|
|
708
|
+
* drafts.forEach(draft => { draft.completed = true })
|
|
709
|
+
* })
|
|
710
|
+
*
|
|
711
|
+
* // Update with metadata
|
|
712
|
+
* update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
|
|
713
|
+
*/
|
|
648
714
|
update<TItem extends object = T>(
|
|
649
|
-
|
|
715
|
+
id: unknown,
|
|
650
716
|
configOrCallback: ((draft: TItem) => void) | OperationConfig,
|
|
651
717
|
maybeCallback?: (draft: TItem) => void
|
|
652
718
|
): Transaction
|
|
653
719
|
|
|
654
720
|
update<TItem extends object = T>(
|
|
655
|
-
|
|
721
|
+
ids: Array<unknown>,
|
|
656
722
|
configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,
|
|
657
723
|
maybeCallback?: (draft: Array<TItem>) => void
|
|
658
724
|
): Transaction
|
|
659
725
|
|
|
660
726
|
update<TItem extends object = T>(
|
|
661
|
-
|
|
727
|
+
ids: unknown | Array<unknown>,
|
|
662
728
|
configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,
|
|
663
729
|
maybeCallback?: (draft: TItem | Array<TItem>) => void
|
|
664
730
|
) {
|
|
665
|
-
if (typeof
|
|
731
|
+
if (typeof ids === `undefined`) {
|
|
666
732
|
throw new Error(`The first argument to update is missing`)
|
|
667
733
|
}
|
|
668
734
|
|
|
@@ -671,28 +737,26 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
671
737
|
throw `no transaction found when calling collection.update`
|
|
672
738
|
}
|
|
673
739
|
|
|
674
|
-
const isArray = Array.isArray(
|
|
675
|
-
const
|
|
740
|
+
const isArray = Array.isArray(ids)
|
|
741
|
+
const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>
|
|
742
|
+
this.getKeyFromId(id)
|
|
743
|
+
)
|
|
676
744
|
const callback =
|
|
677
745
|
typeof configOrCallback === `function` ? configOrCallback : maybeCallback!
|
|
678
746
|
const config =
|
|
679
747
|
typeof configOrCallback === `function` ? {} : configOrCallback
|
|
680
748
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
749
|
+
// Get the current objects or empty objects if they don't exist
|
|
750
|
+
const currentObjects = idsArray.map((id) => {
|
|
751
|
+
const item = this.state.get(id)
|
|
752
|
+
if (!item) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`The id "${id}" was passed to update but an object for this ID was not found in the collection`
|
|
755
|
+
)
|
|
688
756
|
}
|
|
689
|
-
throw new Error(`Invalid item type for update - must be an object`)
|
|
690
|
-
})
|
|
691
757
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
...(this.state.get(key) || {}),
|
|
695
|
-
})) as Array<TItem>
|
|
758
|
+
return item
|
|
759
|
+
}) as unknown as Array<TItem>
|
|
696
760
|
|
|
697
761
|
let changesArray
|
|
698
762
|
if (isArray) {
|
|
@@ -710,29 +774,44 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
710
774
|
}
|
|
711
775
|
|
|
712
776
|
// Create mutations for each object that has changes
|
|
713
|
-
const mutations: Array<PendingMutation<T>> =
|
|
714
|
-
.map((
|
|
715
|
-
const
|
|
777
|
+
const mutations: Array<PendingMutation<T>> = idsArray
|
|
778
|
+
.map((id, index) => {
|
|
779
|
+
const itemChanges = changesArray[index] // User-provided changes for this specific item
|
|
716
780
|
|
|
717
781
|
// Skip items with no changes
|
|
718
|
-
if (!
|
|
782
|
+
if (!itemChanges || Object.keys(itemChanges).length === 0) {
|
|
719
783
|
return null
|
|
720
784
|
}
|
|
721
785
|
|
|
722
|
-
|
|
723
|
-
|
|
786
|
+
const originalItem = currentObjects[index] as unknown as T
|
|
787
|
+
// Validate the user-provided changes for this item
|
|
788
|
+
const validatedUpdatePayload = this.validateData(
|
|
789
|
+
itemChanges,
|
|
790
|
+
`update`,
|
|
791
|
+
id
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
// Construct the full modified item by applying the validated update payload to the original item
|
|
795
|
+
const modifiedItem = { ...originalItem, ...validatedUpdatePayload }
|
|
796
|
+
|
|
797
|
+
// Check if the ID of the item is being changed
|
|
798
|
+
const originalItemId = this.config.getId(originalItem)
|
|
799
|
+
const modifiedItemId = this.config.getId(modifiedItem)
|
|
800
|
+
|
|
801
|
+
if (originalItemId !== modifiedItemId) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
`Updating the ID of an item is not allowed. Original ID: "${originalItemId}", Attempted new ID: "${modifiedItemId}". Please delete the old item and create a new one if an ID change is necessary.`
|
|
804
|
+
)
|
|
805
|
+
}
|
|
724
806
|
|
|
725
807
|
return {
|
|
726
808
|
mutationId: crypto.randomUUID(),
|
|
727
|
-
original:
|
|
728
|
-
modified:
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
} as Record<string, unknown>,
|
|
732
|
-
changes: validatedData as Record<string, unknown>,
|
|
733
|
-
key,
|
|
809
|
+
original: originalItem as Record<string, unknown>,
|
|
810
|
+
modified: modifiedItem as Record<string, unknown>,
|
|
811
|
+
changes: validatedUpdatePayload as Record<string, unknown>,
|
|
812
|
+
key: id,
|
|
734
813
|
metadata: config.metadata as unknown,
|
|
735
|
-
syncMetadata: (this.syncedMetadata.state.get(
|
|
814
|
+
syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<
|
|
736
815
|
string,
|
|
737
816
|
unknown
|
|
738
817
|
>,
|
|
@@ -761,57 +840,39 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
761
840
|
|
|
762
841
|
/**
|
|
763
842
|
* Deletes one or more items from the collection
|
|
764
|
-
* @param
|
|
843
|
+
* @param ids - Single ID or array of IDs to delete
|
|
765
844
|
* @param config - Optional configuration including metadata
|
|
766
845
|
* @returns A Transaction object representing the delete operation(s)
|
|
767
846
|
* @example
|
|
768
847
|
* // Delete a single item
|
|
769
|
-
* delete(todo)
|
|
848
|
+
* delete("todo-1")
|
|
770
849
|
*
|
|
771
850
|
* // Delete multiple items
|
|
772
|
-
* delete([
|
|
851
|
+
* delete(["todo-1", "todo-2"])
|
|
773
852
|
*
|
|
774
853
|
* // Delete with metadata
|
|
775
|
-
* delete(todo, { metadata: { reason: "completed" } })
|
|
854
|
+
* delete("todo-1", { metadata: { reason: "completed" } })
|
|
776
855
|
*/
|
|
777
|
-
delete = (
|
|
778
|
-
items: Array<T | string> | T | string,
|
|
779
|
-
config?: OperationConfig
|
|
780
|
-
) => {
|
|
856
|
+
delete = (ids: Array<string> | string, config?: OperationConfig) => {
|
|
781
857
|
const transaction = getActiveTransaction()
|
|
782
858
|
if (typeof transaction === `undefined`) {
|
|
783
859
|
throw `no transaction found when calling collection.delete`
|
|
784
860
|
}
|
|
785
861
|
|
|
786
|
-
const
|
|
862
|
+
const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>
|
|
863
|
+
this.getKeyFromId(id)
|
|
864
|
+
)
|
|
787
865
|
const mutations: Array<PendingMutation<T>> = []
|
|
788
866
|
|
|
789
|
-
for (const
|
|
790
|
-
let key: string
|
|
791
|
-
if (typeof item === `object` && (item as unknown) !== null) {
|
|
792
|
-
const objectKey = this.objectKeyMap.get(item)
|
|
793
|
-
if (objectKey === undefined) {
|
|
794
|
-
throw new Error(
|
|
795
|
-
`Object not found in collection: ${JSON.stringify(item)}`
|
|
796
|
-
)
|
|
797
|
-
}
|
|
798
|
-
key = objectKey
|
|
799
|
-
} else if (typeof item === `string`) {
|
|
800
|
-
key = item
|
|
801
|
-
} else {
|
|
802
|
-
throw new Error(
|
|
803
|
-
`Invalid item type for delete - must be an object or string key`
|
|
804
|
-
)
|
|
805
|
-
}
|
|
806
|
-
|
|
867
|
+
for (const id of idsArray) {
|
|
807
868
|
const mutation: PendingMutation<T> = {
|
|
808
869
|
mutationId: crypto.randomUUID(),
|
|
809
|
-
original: (this.state.get(
|
|
810
|
-
modified: {
|
|
811
|
-
changes: {
|
|
812
|
-
key,
|
|
870
|
+
original: (this.state.get(id) || {}) as Record<string, unknown>,
|
|
871
|
+
modified: (this.state.get(id) || {}) as Record<string, unknown>,
|
|
872
|
+
changes: (this.state.get(id) || {}) as Record<string, unknown>,
|
|
873
|
+
key: id,
|
|
813
874
|
metadata: config?.metadata as unknown,
|
|
814
|
-
syncMetadata: (this.syncedMetadata.state.get(
|
|
875
|
+
syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<
|
|
815
876
|
string,
|
|
816
877
|
unknown
|
|
817
878
|
>,
|
|
@@ -824,14 +885,6 @@ export class Collection<T extends object = Record<string, unknown>> {
|
|
|
824
885
|
mutations.push(mutation)
|
|
825
886
|
}
|
|
826
887
|
|
|
827
|
-
// Delete object => key mapping.
|
|
828
|
-
mutations.forEach((mutation) => {
|
|
829
|
-
const curValue = this.state.get(mutation.key)
|
|
830
|
-
if (curValue) {
|
|
831
|
-
this.objectKeyMap.delete(curValue)
|
|
832
|
-
}
|
|
833
|
-
})
|
|
834
|
-
|
|
835
888
|
transaction.applyMutations(mutations)
|
|
836
889
|
|
|
837
890
|
this.transactions.setState((sortedMap) => {
|