@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.
Files changed (102) hide show
  1. package/dist/cjs/collection.cjs +113 -94
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +38 -11
  4. package/dist/cjs/index.cjs +1 -0
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/proxy.cjs +87 -248
  7. package/dist/cjs/proxy.cjs.map +1 -1
  8. package/dist/cjs/proxy.d.cts +5 -5
  9. package/dist/cjs/query/compiled-query.cjs +23 -14
  10. package/dist/cjs/query/compiled-query.cjs.map +1 -1
  11. package/dist/cjs/query/compiled-query.d.cts +3 -1
  12. package/dist/cjs/query/evaluators.cjs +20 -20
  13. package/dist/cjs/query/evaluators.cjs.map +1 -1
  14. package/dist/cjs/query/evaluators.d.cts +3 -2
  15. package/dist/cjs/query/extractors.cjs +20 -20
  16. package/dist/cjs/query/extractors.cjs.map +1 -1
  17. package/dist/cjs/query/extractors.d.cts +3 -3
  18. package/dist/cjs/query/group-by.cjs +12 -15
  19. package/dist/cjs/query/group-by.cjs.map +1 -1
  20. package/dist/cjs/query/group-by.d.cts +7 -7
  21. package/dist/cjs/query/joins.cjs +41 -55
  22. package/dist/cjs/query/joins.cjs.map +1 -1
  23. package/dist/cjs/query/joins.d.cts +3 -3
  24. package/dist/cjs/query/order-by.cjs +37 -84
  25. package/dist/cjs/query/order-by.cjs.map +1 -1
  26. package/dist/cjs/query/order-by.d.cts +2 -2
  27. package/dist/cjs/query/pipeline-compiler.cjs +13 -18
  28. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
  29. package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
  30. package/dist/cjs/query/query-builder.cjs +0 -12
  31. package/dist/cjs/query/query-builder.cjs.map +1 -1
  32. package/dist/cjs/query/query-builder.d.cts +4 -8
  33. package/dist/cjs/query/schema.d.cts +1 -6
  34. package/dist/cjs/query/select.cjs +35 -24
  35. package/dist/cjs/query/select.cjs.map +1 -1
  36. package/dist/cjs/query/select.d.cts +2 -2
  37. package/dist/cjs/query/types.d.cts +1 -0
  38. package/dist/cjs/transactions.cjs +17 -8
  39. package/dist/cjs/transactions.cjs.map +1 -1
  40. package/dist/cjs/types.d.cts +41 -7
  41. package/dist/esm/collection.d.ts +38 -11
  42. package/dist/esm/collection.js +113 -94
  43. package/dist/esm/collection.js.map +1 -1
  44. package/dist/esm/index.js +2 -1
  45. package/dist/esm/proxy.d.ts +5 -5
  46. package/dist/esm/proxy.js +87 -248
  47. package/dist/esm/proxy.js.map +1 -1
  48. package/dist/esm/query/compiled-query.d.ts +3 -1
  49. package/dist/esm/query/compiled-query.js +23 -14
  50. package/dist/esm/query/compiled-query.js.map +1 -1
  51. package/dist/esm/query/evaluators.d.ts +3 -2
  52. package/dist/esm/query/evaluators.js +21 -21
  53. package/dist/esm/query/evaluators.js.map +1 -1
  54. package/dist/esm/query/extractors.d.ts +3 -3
  55. package/dist/esm/query/extractors.js +20 -20
  56. package/dist/esm/query/extractors.js.map +1 -1
  57. package/dist/esm/query/group-by.d.ts +7 -7
  58. package/dist/esm/query/group-by.js +14 -17
  59. package/dist/esm/query/group-by.js.map +1 -1
  60. package/dist/esm/query/joins.d.ts +3 -3
  61. package/dist/esm/query/joins.js +42 -56
  62. package/dist/esm/query/joins.js.map +1 -1
  63. package/dist/esm/query/order-by.d.ts +2 -2
  64. package/dist/esm/query/order-by.js +39 -86
  65. package/dist/esm/query/order-by.js.map +1 -1
  66. package/dist/esm/query/pipeline-compiler.d.ts +2 -1
  67. package/dist/esm/query/pipeline-compiler.js +14 -19
  68. package/dist/esm/query/pipeline-compiler.js.map +1 -1
  69. package/dist/esm/query/query-builder.d.ts +4 -8
  70. package/dist/esm/query/query-builder.js +0 -12
  71. package/dist/esm/query/query-builder.js.map +1 -1
  72. package/dist/esm/query/schema.d.ts +1 -6
  73. package/dist/esm/query/select.d.ts +2 -2
  74. package/dist/esm/query/select.js +36 -25
  75. package/dist/esm/query/select.js.map +1 -1
  76. package/dist/esm/query/types.d.ts +1 -0
  77. package/dist/esm/transactions.js +17 -8
  78. package/dist/esm/transactions.js.map +1 -1
  79. package/dist/esm/types.d.ts +41 -7
  80. package/package.json +2 -2
  81. package/src/collection.ts +174 -121
  82. package/src/proxy.ts +141 -358
  83. package/src/query/compiled-query.ts +30 -15
  84. package/src/query/evaluators.ts +22 -21
  85. package/src/query/extractors.ts +24 -21
  86. package/src/query/group-by.ts +24 -22
  87. package/src/query/joins.ts +88 -75
  88. package/src/query/order-by.ts +56 -106
  89. package/src/query/pipeline-compiler.ts +34 -37
  90. package/src/query/query-builder.ts +9 -23
  91. package/src/query/schema.ts +1 -10
  92. package/src/query/select.ts +44 -32
  93. package/src/query/types.ts +1 -0
  94. package/src/transactions.ts +22 -13
  95. package/src/types.ts +48 -7
  96. package/dist/cjs/query/key-by.cjs +0 -43
  97. package/dist/cjs/query/key-by.cjs.map +0 -1
  98. package/dist/cjs/query/key-by.d.cts +0 -3
  99. package/dist/esm/query/key-by.d.ts +0 -3
  100. package/dist/esm/query/key-by.js +0 -43
  101. package/dist/esm/query/key-by.js.map +0 -1
  102. 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 = crypto.randomUUID()
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?: CollectionConfig<T>) {
184
- if (!config?.sync) {
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: (message: ChangeMessage<T>) => {
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
- let keys: Array<string>
583
- if (config?.key) {
584
- const configKeys = Array.isArray(config.key) ? config.key : [config.key]
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
- item: TItem,
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
- items: Array<TItem>,
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
- items: TItem | Array<TItem>,
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 items === `undefined`) {
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(items)
675
- const itemsArray = Array.isArray(items) ? items : [items]
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
- const keys = itemsArray.map((item) => {
682
- if (typeof item === `object` && (item as unknown) !== null) {
683
- const key = this.objectKeyMap.get(item)
684
- if (key === undefined) {
685
- throw new Error(`Object not found in collection`)
686
- }
687
- return key
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
- // Get the current objects or empty objects if they don't exist
693
- const currentObjects = keys.map((key) => ({
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>> = keys
714
- .map((key, index) => {
715
- const changes = changesArray[index]
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 (!changes || Object.keys(changes).length === 0) {
782
+ if (!itemChanges || Object.keys(itemChanges).length === 0) {
719
783
  return null
720
784
  }
721
785
 
722
- // Validate the changes for this item
723
- const validatedData = this.validateData(changes, `update`, key)
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: (this.state.get(key) || {}) as Record<string, unknown>,
728
- modified: {
729
- ...(this.state.get(key) || {}),
730
- ...validatedData,
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(key) || {}) as Record<
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 items - Single item/key or array of items/keys to delete
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([todo1, todo2])
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 itemsArray = Array.isArray(items) ? items : [items]
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 item of itemsArray) {
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(key) || {}) as Record<string, unknown>,
810
- modified: { _deleted: true },
811
- changes: { _deleted: true },
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(key) || {}) as Record<
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) => {