@tanstack/db 0.0.17 → 0.0.19

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 (51) hide show
  1. package/dist/cjs/SortedMap.cjs.map +1 -1
  2. package/dist/cjs/collection.cjs +47 -6
  3. package/dist/cjs/collection.cjs.map +1 -1
  4. package/dist/cjs/collection.d.cts +150 -45
  5. package/dist/cjs/deferred.cjs.map +1 -1
  6. package/dist/cjs/errors.cjs.map +1 -1
  7. package/dist/cjs/optimistic-action.cjs.map +1 -1
  8. package/dist/cjs/proxy.cjs.map +1 -1
  9. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  10. package/dist/cjs/query/builder/index.cjs.map +1 -1
  11. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  12. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  13. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  14. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  15. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  16. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  17. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  18. package/dist/cjs/query/ir.cjs.map +1 -1
  19. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  20. package/dist/cjs/transactions.cjs +116 -0
  21. package/dist/cjs/transactions.cjs.map +1 -1
  22. package/dist/cjs/transactions.d.cts +179 -0
  23. package/dist/cjs/types.d.cts +169 -13
  24. package/dist/esm/SortedMap.js.map +1 -1
  25. package/dist/esm/collection.d.ts +150 -45
  26. package/dist/esm/collection.js +47 -6
  27. package/dist/esm/collection.js.map +1 -1
  28. package/dist/esm/deferred.js.map +1 -1
  29. package/dist/esm/errors.js.map +1 -1
  30. package/dist/esm/optimistic-action.js.map +1 -1
  31. package/dist/esm/proxy.js.map +1 -1
  32. package/dist/esm/query/builder/functions.js.map +1 -1
  33. package/dist/esm/query/builder/index.js.map +1 -1
  34. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  35. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  36. package/dist/esm/query/compiler/group-by.js.map +1 -1
  37. package/dist/esm/query/compiler/index.js.map +1 -1
  38. package/dist/esm/query/compiler/joins.js.map +1 -1
  39. package/dist/esm/query/compiler/order-by.js.map +1 -1
  40. package/dist/esm/query/compiler/select.js.map +1 -1
  41. package/dist/esm/query/ir.js.map +1 -1
  42. package/dist/esm/query/live-query-collection.js.map +1 -1
  43. package/dist/esm/transactions.d.ts +179 -0
  44. package/dist/esm/transactions.js +116 -0
  45. package/dist/esm/transactions.js.map +1 -1
  46. package/dist/esm/types.d.ts +169 -13
  47. package/package.json +1 -1
  48. package/src/collection.ts +165 -50
  49. package/src/proxy.ts +0 -1
  50. package/src/transactions.ts +179 -0
  51. package/src/types.ts +199 -28
package/src/collection.ts CHANGED
@@ -53,12 +53,52 @@ export interface Collection<
53
53
  * @returns A new Collection with utilities exposed both at top level and under .utils
54
54
  *
55
55
  * @example
56
- * // Using explicit type
57
- * const todos = createCollection<Todo>({
56
+ * // Pattern 1: With operation handlers (direct collection calls)
57
+ * const todos = createCollection({
58
+ * id: "todos",
59
+ * getKey: (todo) => todo.id,
60
+ * schema,
61
+ * onInsert: async ({ transaction, collection }) => {
62
+ * // Send to API
63
+ * await api.createTodo(transaction.mutations[0].modified)
64
+ * },
65
+ * onUpdate: async ({ transaction, collection }) => {
66
+ * await api.updateTodo(transaction.mutations[0].modified)
67
+ * },
68
+ * onDelete: async ({ transaction, collection }) => {
69
+ * await api.deleteTodo(transaction.mutations[0].key)
70
+ * },
71
+ * sync: { sync: () => {} }
72
+ * })
73
+ *
74
+ * // Direct usage (handlers manage transactions)
75
+ * const tx = todos.insert({ id: "1", text: "Buy milk", completed: false })
76
+ * await tx.isPersisted.promise
77
+ *
78
+ * @example
79
+ * // Pattern 2: Manual transaction management
80
+ * const todos = createCollection({
58
81
  * getKey: (todo) => todo.id,
82
+ * schema: todoSchema,
59
83
  * sync: { sync: () => {} }
60
84
  * })
61
85
  *
86
+ * // Explicit transaction usage
87
+ * const tx = createTransaction({
88
+ * mutationFn: async ({ transaction }) => {
89
+ * // Handle all mutations in transaction
90
+ * await api.saveChanges(transaction.mutations)
91
+ * }
92
+ * })
93
+ *
94
+ * tx.mutate(() => {
95
+ * todos.insert({ id: "1", text: "Buy milk" })
96
+ * todos.update("2", draft => { draft.completed = true })
97
+ * })
98
+ *
99
+ * await tx.isPersisted.promise
100
+ *
101
+ * @example
62
102
  * // Using schema for type inference (preferred as it also gives you client side validation)
63
103
  * const todoSchema = z.object({
64
104
  * id: z.string(),
@@ -72,7 +112,7 @@ export interface Collection<
72
112
  * sync: { sync: () => {} }
73
113
  * })
74
114
  *
75
- * // Note: You must provide either an explicit type or a schema, but not both
115
+ * // Note: You must provide either an explicit type or a schema, but not both.
76
116
  */
77
117
  export function createCollection<
78
118
  TExplicit = unknown,
@@ -138,6 +178,7 @@ export class SchemaValidationError extends Error {
138
178
  export class CollectionImpl<
139
179
  T extends object = Record<string, unknown>,
140
180
  TKey extends string | number = string | number,
181
+ TUtils extends UtilsRecord = {},
141
182
  > {
142
183
  public config: CollectionConfig<T, TKey, any>
143
184
 
@@ -187,6 +228,11 @@ export class CollectionImpl<
187
228
  * Register a callback to be executed on the next commit
188
229
  * Useful for preloading collections
189
230
  * @param callback Function to call after the next commit
231
+ * @example
232
+ * collection.onFirstCommit(() => {
233
+ * console.log('Collection has received first data')
234
+ * // Safe to access collection.state now
235
+ * })
190
236
  */
191
237
  public onFirstCommit(callback: () => void): void {
192
238
  this.onFirstCommitCallbacks.push(callback)
@@ -1243,21 +1289,38 @@ export class CollectionImpl<
1243
1289
  /**
1244
1290
  * Inserts one or more items into the collection
1245
1291
  * @param items - Single item or array of items to insert
1246
- * @param config - Optional configuration including metadata and custom keys
1247
- * @returns A TransactionType object representing the insert operation(s)
1292
+ * @param config - Optional configuration including metadata
1293
+ * @returns A Transaction object representing the insert operation(s)
1248
1294
  * @throws {SchemaValidationError} If the data fails schema validation
1249
1295
  * @example
1250
- * // Insert a single item
1251
- * insert({ text: "Buy groceries", completed: false })
1296
+ * // Insert a single todo (requires onInsert handler)
1297
+ * const tx = collection.insert({ id: "1", text: "Buy milk", completed: false })
1298
+ * await tx.isPersisted.promise
1252
1299
  *
1253
- * // Insert multiple items
1254
- * insert([
1255
- * { text: "Buy groceries", completed: false },
1256
- * { text: "Walk dog", completed: false }
1300
+ * @example
1301
+ * // Insert multiple todos at once
1302
+ * const tx = collection.insert([
1303
+ * { id: "1", text: "Buy milk", completed: false },
1304
+ * { id: "2", text: "Walk dog", completed: true }
1257
1305
  * ])
1306
+ * await tx.isPersisted.promise
1258
1307
  *
1259
- * // Insert with custom key
1260
- * insert({ text: "Buy groceries" }, { key: "grocery-task" })
1308
+ * @example
1309
+ * // Insert with metadata
1310
+ * const tx = collection.insert({ id: "1", text: "Buy groceries" },
1311
+ * { metadata: { source: "mobile-app" } }
1312
+ * )
1313
+ * await tx.isPersisted.promise
1314
+ *
1315
+ * @example
1316
+ * // Handle errors
1317
+ * try {
1318
+ * const tx = collection.insert({ id: "1", text: "New item" })
1319
+ * await tx.isPersisted.promise
1320
+ * console.log('Insert successful')
1321
+ * } catch (error) {
1322
+ * console.log('Insert failed:', error)
1323
+ * }
1261
1324
  */
1262
1325
  insert = (data: T | Array<T>, config?: InsertConfig) => {
1263
1326
  this.validateCollectionUsable(`insert`)
@@ -1316,8 +1379,11 @@ export class CollectionImpl<
1316
1379
  // Create a new transaction with a mutation function that calls the onInsert handler
1317
1380
  const directOpTransaction = createTransaction<T>({
1318
1381
  mutationFn: async (params) => {
1319
- // Call the onInsert handler with the transaction
1320
- return this.config.onInsert!(params)
1382
+ // Call the onInsert handler with the transaction and collection
1383
+ return this.config.onInsert!({
1384
+ ...params,
1385
+ collection: this as unknown as Collection<T, TKey, TUtils>,
1386
+ })
1321
1387
  },
1322
1388
  })
1323
1389
 
@@ -1335,43 +1401,44 @@ export class CollectionImpl<
1335
1401
 
1336
1402
  /**
1337
1403
  * Updates one or more items in the collection using a callback function
1338
- * @param items - Single item/key or array of items/keys to update
1404
+ * @param keys - Single key or array of keys to update
1339
1405
  * @param configOrCallback - Either update configuration or update callback
1340
1406
  * @param maybeCallback - Update callback if config was provided
1341
1407
  * @returns A Transaction object representing the update operation(s)
1342
1408
  * @throws {SchemaValidationError} If the updated data fails schema validation
1343
1409
  * @example
1344
- * // Update a single item
1345
- * update(todo, (draft) => { draft.completed = true })
1346
- *
1347
- * // Update multiple items
1348
- * update([todo1, todo2], (drafts) => {
1349
- * drafts.forEach(draft => { draft.completed = true })
1410
+ * // Update single item by key
1411
+ * const tx = collection.update("todo-1", (draft) => {
1412
+ * draft.completed = true
1350
1413
  * })
1414
+ * await tx.isPersisted.promise
1351
1415
  *
1352
- * // Update with metadata
1353
- * update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
1354
- */
1355
-
1356
- /**
1357
- * Updates one or more items in the collection using a callback function
1358
- * @param ids - Single ID or array of IDs to update
1359
- * @param configOrCallback - Either update configuration or update callback
1360
- * @param maybeCallback - Update callback if config was provided
1361
- * @returns A Transaction object representing the update operation(s)
1362
- * @throws {SchemaValidationError} If the updated data fails schema validation
1363
1416
  * @example
1364
- * // Update a single item
1365
- * update("todo-1", (draft) => { draft.completed = true })
1366
- *
1367
1417
  * // Update multiple items
1368
- * update(["todo-1", "todo-2"], (drafts) => {
1418
+ * const tx = collection.update(["todo-1", "todo-2"], (drafts) => {
1369
1419
  * drafts.forEach(draft => { draft.completed = true })
1370
1420
  * })
1421
+ * await tx.isPersisted.promise
1371
1422
  *
1423
+ * @example
1372
1424
  * // Update with metadata
1373
- * update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
1425
+ * const tx = collection.update("todo-1",
1426
+ * { metadata: { reason: "user update" } },
1427
+ * (draft) => { draft.text = "Updated text" }
1428
+ * )
1429
+ * await tx.isPersisted.promise
1430
+ *
1431
+ * @example
1432
+ * // Handle errors
1433
+ * try {
1434
+ * const tx = collection.update("item-1", draft => { draft.value = "new" })
1435
+ * await tx.isPersisted.promise
1436
+ * console.log('Update successful')
1437
+ * } catch (error) {
1438
+ * console.log('Update failed:', error)
1439
+ * }
1374
1440
  */
1441
+
1375
1442
  // Overload 1: Update multiple items with a callback
1376
1443
  update<TItem extends object = T>(
1377
1444
  key: Array<TKey | unknown>,
@@ -1538,8 +1605,11 @@ export class CollectionImpl<
1538
1605
  // Create a new transaction with a mutation function that calls the onUpdate handler
1539
1606
  const directOpTransaction = createTransaction<T>({
1540
1607
  mutationFn: async (params) => {
1541
- // Call the onUpdate handler with the transaction
1542
- return this.config.onUpdate!(params)
1608
+ // Call the onUpdate handler with the transaction and collection
1609
+ return this.config.onUpdate!({
1610
+ ...params,
1611
+ collection: this as unknown as Collection<T, TKey, TUtils>,
1612
+ })
1543
1613
  },
1544
1614
  })
1545
1615
 
@@ -1557,18 +1627,33 @@ export class CollectionImpl<
1557
1627
 
1558
1628
  /**
1559
1629
  * Deletes one or more items from the collection
1560
- * @param ids - Single ID or array of IDs to delete
1630
+ * @param keys - Single key or array of keys to delete
1561
1631
  * @param config - Optional configuration including metadata
1562
- * @returns A TransactionType object representing the delete operation(s)
1632
+ * @returns A Transaction object representing the delete operation(s)
1563
1633
  * @example
1564
1634
  * // Delete a single item
1565
- * delete("todo-1")
1635
+ * const tx = collection.delete("todo-1")
1636
+ * await tx.isPersisted.promise
1566
1637
  *
1638
+ * @example
1567
1639
  * // Delete multiple items
1568
- * delete(["todo-1", "todo-2"])
1640
+ * const tx = collection.delete(["todo-1", "todo-2"])
1641
+ * await tx.isPersisted.promise
1569
1642
  *
1643
+ * @example
1570
1644
  * // Delete with metadata
1571
- * delete("todo-1", { metadata: { reason: "completed" } })
1645
+ * const tx = collection.delete("todo-1", { metadata: { reason: "completed" } })
1646
+ * await tx.isPersisted.promise
1647
+ *
1648
+ * @example
1649
+ * // Handle errors
1650
+ * try {
1651
+ * const tx = collection.delete("item-1")
1652
+ * await tx.isPersisted.promise
1653
+ * console.log('Delete successful')
1654
+ * } catch (error) {
1655
+ * console.log('Delete failed:', error)
1656
+ * }
1572
1657
  */
1573
1658
  delete = (
1574
1659
  keys: Array<TKey> | TKey,
@@ -1634,8 +1719,11 @@ export class CollectionImpl<
1634
1719
  const directOpTransaction = createTransaction<T>({
1635
1720
  autoCommit: true,
1636
1721
  mutationFn: async (params) => {
1637
- // Call the onDelete handler with the transaction
1638
- return this.config.onDelete!(params)
1722
+ // Call the onDelete handler with the transaction and collection
1723
+ return this.config.onDelete!({
1724
+ ...params,
1725
+ collection: this as unknown as Collection<T, TKey, TUtils>,
1726
+ })
1639
1727
  },
1640
1728
  })
1641
1729
 
@@ -1651,8 +1739,19 @@ export class CollectionImpl<
1651
1739
 
1652
1740
  /**
1653
1741
  * Gets the current state of the collection as a Map
1742
+ * @returns Map containing all items in the collection, with keys as identifiers
1743
+ * @example
1744
+ * const itemsMap = collection.state
1745
+ * console.log(`Collection has ${itemsMap.size} items`)
1654
1746
  *
1655
- * @returns A Map containing all items in the collection, with keys as identifiers
1747
+ * for (const [key, item] of itemsMap) {
1748
+ * console.log(`${key}: ${item.title}`)
1749
+ * }
1750
+ *
1751
+ * // Check if specific item exists
1752
+ * if (itemsMap.has("todo-1")) {
1753
+ * console.log("Todo 1 exists:", itemsMap.get("todo-1"))
1754
+ * }
1656
1755
  */
1657
1756
  get state() {
1658
1757
  const result = new Map<TKey, T>()
@@ -1725,8 +1824,24 @@ export class CollectionImpl<
1725
1824
 
1726
1825
  /**
1727
1826
  * Subscribe to changes in the collection
1728
- * @param callback - A function that will be called with the changes in the collection
1729
- * @returns A function that can be called to unsubscribe from the changes
1827
+ * @param callback - Function called when items change
1828
+ * @param options.includeInitialState - If true, immediately calls callback with current data
1829
+ * @returns Unsubscribe function - Call this to stop listening for changes
1830
+ * @example
1831
+ * // Basic subscription
1832
+ * const unsubscribe = collection.subscribeChanges((changes) => {
1833
+ * changes.forEach(change => {
1834
+ * console.log(`${change.type}: ${change.key}`, change.value)
1835
+ * })
1836
+ * })
1837
+ *
1838
+ * // Later: unsubscribe()
1839
+ *
1840
+ * @example
1841
+ * // Include current state immediately
1842
+ * const unsubscribe = collection.subscribeChanges((changes) => {
1843
+ * updateUI(changes)
1844
+ * }, { includeInitialState: true })
1730
1845
  */
1731
1846
  public subscribeChanges(
1732
1847
  callback: (changes: Array<ChangeMessage<T>>) => void,
package/src/proxy.ts CHANGED
@@ -505,7 +505,6 @@ export function createChangeProxy<
505
505
  if (typeof callback === `function`) {
506
506
  // Replace the original callback with our wrapped version
507
507
  const wrappedCallback = function (
508
- // eslint-disable-next-line
509
508
  this: unknown,
510
509
  // eslint-disable-next-line
511
510
  value: unknown,
@@ -14,6 +14,58 @@ let transactionStack: Array<Transaction<any>> = []
14
14
 
15
15
  let sequenceNumber = 0
16
16
 
17
+ /**
18
+ * Creates a new transaction for grouping multiple collection operations
19
+ * @param config - Transaction configuration with mutation function
20
+ * @returns A new Transaction instance
21
+ * @example
22
+ * // Basic transaction usage
23
+ * const tx = createTransaction({
24
+ * mutationFn: async ({ transaction }) => {
25
+ * // Send all mutations to API
26
+ * await api.saveChanges(transaction.mutations)
27
+ * }
28
+ * })
29
+ *
30
+ * tx.mutate(() => {
31
+ * collection.insert({ id: "1", text: "Buy milk" })
32
+ * collection.update("2", draft => { draft.completed = true })
33
+ * })
34
+ *
35
+ * await tx.isPersisted.promise
36
+ *
37
+ * @example
38
+ * // Handle transaction errors
39
+ * try {
40
+ * const tx = createTransaction({
41
+ * mutationFn: async () => { throw new Error("API failed") }
42
+ * })
43
+ *
44
+ * tx.mutate(() => {
45
+ * collection.insert({ id: "1", text: "New item" })
46
+ * })
47
+ *
48
+ * await tx.isPersisted.promise
49
+ * } catch (error) {
50
+ * console.log('Transaction failed:', error)
51
+ * }
52
+ *
53
+ * @example
54
+ * // Manual commit control
55
+ * const tx = createTransaction({
56
+ * autoCommit: false,
57
+ * mutationFn: async () => {
58
+ * // API call
59
+ * }
60
+ * })
61
+ *
62
+ * tx.mutate(() => {
63
+ * collection.insert({ id: "1", text: "Item" })
64
+ * })
65
+ *
66
+ * // Commit later
67
+ * await tx.commit()
68
+ */
17
69
  export function createTransaction<
18
70
  TData extends object = Record<string, unknown>,
19
71
  >(config: TransactionConfig<TData>): Transaction<TData> {
@@ -22,6 +74,17 @@ export function createTransaction<
22
74
  return newTransaction
23
75
  }
24
76
 
77
+ /**
78
+ * Gets the currently active ambient transaction, if any
79
+ * Used internally by collection operations to join existing transactions
80
+ * @returns The active transaction or undefined if none is active
81
+ * @example
82
+ * // Check if operations will join an ambient transaction
83
+ * const ambientTx = getActiveTransaction()
84
+ * if (ambientTx) {
85
+ * console.log('Operations will join transaction:', ambientTx.id)
86
+ * }
87
+ */
25
88
  export function getActiveTransaction(): Transaction | undefined {
26
89
  if (transactionStack.length > 0) {
27
90
  return transactionStack.slice(-1)[0]
@@ -86,6 +149,45 @@ class Transaction<
86
149
  }
87
150
  }
88
151
 
152
+ /**
153
+ * Execute collection operations within this transaction
154
+ * @param callback - Function containing collection operations to group together
155
+ * @returns This transaction for chaining
156
+ * @example
157
+ * // Group multiple operations
158
+ * const tx = createTransaction({ mutationFn: async () => {
159
+ * // Send to API
160
+ * }})
161
+ *
162
+ * tx.mutate(() => {
163
+ * collection.insert({ id: "1", text: "Buy milk" })
164
+ * collection.update("2", draft => { draft.completed = true })
165
+ * collection.delete("3")
166
+ * })
167
+ *
168
+ * await tx.isPersisted.promise
169
+ *
170
+ * @example
171
+ * // Handle mutate errors
172
+ * try {
173
+ * tx.mutate(() => {
174
+ * collection.insert({ id: "invalid" }) // This might throw
175
+ * })
176
+ * } catch (error) {
177
+ * console.log('Mutation failed:', error)
178
+ * }
179
+ *
180
+ * @example
181
+ * // Manual commit control
182
+ * const tx = createTransaction({ autoCommit: false, mutationFn: async () => {} })
183
+ *
184
+ * tx.mutate(() => {
185
+ * collection.insert({ id: "1", text: "Item" })
186
+ * })
187
+ *
188
+ * // Commit later when ready
189
+ * await tx.commit()
190
+ */
89
191
  mutate(callback: () => void): Transaction<T> {
90
192
  if (this.state !== `pending`) {
91
193
  throw `You can no longer call .mutate() as the transaction is no longer pending`
@@ -121,6 +223,44 @@ class Transaction<
121
223
  }
122
224
  }
123
225
 
226
+ /**
227
+ * Rollback the transaction and any conflicting transactions
228
+ * @param config - Configuration for rollback behavior
229
+ * @returns This transaction for chaining
230
+ * @example
231
+ * // Manual rollback
232
+ * const tx = createTransaction({ mutationFn: async () => {
233
+ * // Send to API
234
+ * }})
235
+ *
236
+ * tx.mutate(() => {
237
+ * collection.insert({ id: "1", text: "Buy milk" })
238
+ * })
239
+ *
240
+ * // Rollback if needed
241
+ * if (shouldCancel) {
242
+ * tx.rollback()
243
+ * }
244
+ *
245
+ * @example
246
+ * // Handle rollback cascade (automatic)
247
+ * const tx1 = createTransaction({ mutationFn: async () => {} })
248
+ * const tx2 = createTransaction({ mutationFn: async () => {} })
249
+ *
250
+ * tx1.mutate(() => collection.update("1", draft => { draft.value = "A" }))
251
+ * tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same item
252
+ *
253
+ * tx1.rollback() // This will also rollback tx2 due to conflict
254
+ *
255
+ * @example
256
+ * // Handle rollback in error scenarios
257
+ * try {
258
+ * await tx.isPersisted.promise
259
+ * } catch (error) {
260
+ * console.log('Transaction was rolled back:', error)
261
+ * // Transaction automatically rolled back on mutation function failure
262
+ * }
263
+ */
124
264
  rollback(config?: { isSecondaryRollback?: boolean }): Transaction<T> {
125
265
  const isSecondaryRollback = config?.isSecondaryRollback ?? false
126
266
  if (this.state === `completed`) {
@@ -165,6 +305,45 @@ class Transaction<
165
305
  }
166
306
  }
167
307
 
308
+ /**
309
+ * Commit the transaction and execute the mutation function
310
+ * @returns Promise that resolves to this transaction when complete
311
+ * @example
312
+ * // Manual commit (when autoCommit is false)
313
+ * const tx = createTransaction({
314
+ * autoCommit: false,
315
+ * mutationFn: async ({ transaction }) => {
316
+ * await api.saveChanges(transaction.mutations)
317
+ * }
318
+ * })
319
+ *
320
+ * tx.mutate(() => {
321
+ * collection.insert({ id: "1", text: "Buy milk" })
322
+ * })
323
+ *
324
+ * await tx.commit() // Manually commit
325
+ *
326
+ * @example
327
+ * // Handle commit errors
328
+ * try {
329
+ * const tx = createTransaction({
330
+ * mutationFn: async () => { throw new Error("API failed") }
331
+ * })
332
+ *
333
+ * tx.mutate(() => {
334
+ * collection.insert({ id: "1", text: "Item" })
335
+ * })
336
+ *
337
+ * await tx.commit()
338
+ * } catch (error) {
339
+ * console.log('Commit failed, transaction rolled back:', error)
340
+ * }
341
+ *
342
+ * @example
343
+ * // Check transaction state after commit
344
+ * await tx.commit()
345
+ * console.log(tx.state) // "completed" or "failed"
346
+ */
168
347
  async commit(): Promise<Transaction<T>> {
169
348
  if (this.state !== `pending`) {
170
349
  throw `You can no longer call .commit() as the transaction is no longer pending`