@tanstack/db 0.0.16 → 0.0.18

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 (54) hide show
  1. package/dist/cjs/SortedMap.cjs.map +1 -1
  2. package/dist/cjs/collection.cjs +53 -8
  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 +1 -1
  20. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  21. package/dist/cjs/transactions.cjs +116 -0
  22. package/dist/cjs/transactions.cjs.map +1 -1
  23. package/dist/cjs/transactions.d.cts +179 -0
  24. package/dist/cjs/types.d.cts +170 -12
  25. package/dist/esm/SortedMap.js.map +1 -1
  26. package/dist/esm/collection.d.ts +150 -45
  27. package/dist/esm/collection.js +53 -8
  28. package/dist/esm/collection.js.map +1 -1
  29. package/dist/esm/deferred.js.map +1 -1
  30. package/dist/esm/errors.js.map +1 -1
  31. package/dist/esm/optimistic-action.js.map +1 -1
  32. package/dist/esm/proxy.js.map +1 -1
  33. package/dist/esm/query/builder/functions.js.map +1 -1
  34. package/dist/esm/query/builder/index.js.map +1 -1
  35. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  36. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  37. package/dist/esm/query/compiler/group-by.js.map +1 -1
  38. package/dist/esm/query/compiler/index.js.map +1 -1
  39. package/dist/esm/query/compiler/joins.js.map +1 -1
  40. package/dist/esm/query/compiler/order-by.js.map +1 -1
  41. package/dist/esm/query/compiler/select.js.map +1 -1
  42. package/dist/esm/query/ir.js.map +1 -1
  43. package/dist/esm/query/live-query-collection.js +1 -1
  44. package/dist/esm/query/live-query-collection.js.map +1 -1
  45. package/dist/esm/transactions.d.ts +179 -0
  46. package/dist/esm/transactions.js +116 -0
  47. package/dist/esm/transactions.js.map +1 -1
  48. package/dist/esm/types.d.ts +170 -12
  49. package/package.json +2 -2
  50. package/src/collection.ts +175 -55
  51. package/src/proxy.ts +0 -1
  52. package/src/query/live-query-collection.ts +2 -1
  53. package/src/transactions.ts +179 -0
  54. package/src/types.ts +200 -27
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",
58
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
+ * },
59
71
  * sync: { sync: () => {} }
60
72
  * })
61
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({
81
+ * getKey: (todo) => todo.id,
82
+ * schema: todoSchema,
83
+ * sync: { sync: () => {} }
84
+ * })
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)
@@ -237,7 +283,8 @@ export class CollectionImpl<
237
283
  Array<CollectionStatus>
238
284
  > = {
239
285
  idle: [`loading`, `error`, `cleaned-up`],
240
- loading: [`ready`, `error`, `cleaned-up`],
286
+ loading: [`initialCommit`, `error`, `cleaned-up`],
287
+ initialCommit: [`ready`, `error`, `cleaned-up`],
241
288
  ready: [`cleaned-up`, `error`],
242
289
  error: [`cleaned-up`, `idle`],
243
290
  "cleaned-up": [`loading`, `error`],
@@ -382,14 +429,18 @@ export class CollectionImpl<
382
429
 
383
430
  pendingTransaction.committed = true
384
431
 
385
- // Update status to ready
386
- // We do this before committing as we want the events from the changes to
387
- // be from a "ready" state.
432
+ // Update status to initialCommit when transitioning from loading
433
+ // This indicates we're in the process of committing the first transaction
388
434
  if (this._status === `loading`) {
389
- this.setStatus(`ready`)
435
+ this.setStatus(`initialCommit`)
390
436
  }
391
437
 
392
438
  this.commitPendingTransactions()
439
+
440
+ // Transition from initialCommit to ready after the first commit is complete
441
+ if (this._status === `initialCommit`) {
442
+ this.setStatus(`ready`)
443
+ }
393
444
  },
394
445
  })
395
446
 
@@ -1238,21 +1289,38 @@ export class CollectionImpl<
1238
1289
  /**
1239
1290
  * Inserts one or more items into the collection
1240
1291
  * @param items - Single item or array of items to insert
1241
- * @param config - Optional configuration including metadata and custom keys
1242
- * @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)
1243
1294
  * @throws {SchemaValidationError} If the data fails schema validation
1244
1295
  * @example
1245
- * // Insert a single item
1246
- * 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
1247
1299
  *
1248
- * // Insert multiple items
1249
- * insert([
1250
- * { text: "Buy groceries", completed: false },
1251
- * { 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 }
1252
1305
  * ])
1306
+ * await tx.isPersisted.promise
1307
+ *
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
1253
1314
  *
1254
- * // Insert with custom key
1255
- * insert({ text: "Buy groceries" }, { key: "grocery-task" })
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
+ * }
1256
1324
  */
1257
1325
  insert = (data: T | Array<T>, config?: InsertConfig) => {
1258
1326
  this.validateCollectionUsable(`insert`)
@@ -1311,8 +1379,11 @@ export class CollectionImpl<
1311
1379
  // Create a new transaction with a mutation function that calls the onInsert handler
1312
1380
  const directOpTransaction = createTransaction<T>({
1313
1381
  mutationFn: async (params) => {
1314
- // Call the onInsert handler with the transaction
1315
- 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
+ })
1316
1387
  },
1317
1388
  })
1318
1389
 
@@ -1330,43 +1401,44 @@ export class CollectionImpl<
1330
1401
 
1331
1402
  /**
1332
1403
  * Updates one or more items in the collection using a callback function
1333
- * @param items - Single item/key or array of items/keys to update
1404
+ * @param keys - Single key or array of keys to update
1334
1405
  * @param configOrCallback - Either update configuration or update callback
1335
1406
  * @param maybeCallback - Update callback if config was provided
1336
1407
  * @returns A Transaction object representing the update operation(s)
1337
1408
  * @throws {SchemaValidationError} If the updated data fails schema validation
1338
1409
  * @example
1339
- * // Update a single item
1340
- * update(todo, (draft) => { draft.completed = true })
1341
- *
1342
- * // Update multiple items
1343
- * update([todo1, todo2], (drafts) => {
1344
- * drafts.forEach(draft => { draft.completed = true })
1410
+ * // Update single item by key
1411
+ * const tx = collection.update("todo-1", (draft) => {
1412
+ * draft.completed = true
1345
1413
  * })
1414
+ * await tx.isPersisted.promise
1346
1415
  *
1347
- * // Update with metadata
1348
- * update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
1349
- */
1350
-
1351
- /**
1352
- * Updates one or more items in the collection using a callback function
1353
- * @param ids - Single ID or array of IDs to update
1354
- * @param configOrCallback - Either update configuration or update callback
1355
- * @param maybeCallback - Update callback if config was provided
1356
- * @returns A Transaction object representing the update operation(s)
1357
- * @throws {SchemaValidationError} If the updated data fails schema validation
1358
1416
  * @example
1359
- * // Update a single item
1360
- * update("todo-1", (draft) => { draft.completed = true })
1361
- *
1362
1417
  * // Update multiple items
1363
- * update(["todo-1", "todo-2"], (drafts) => {
1418
+ * const tx = collection.update(["todo-1", "todo-2"], (drafts) => {
1364
1419
  * drafts.forEach(draft => { draft.completed = true })
1365
1420
  * })
1421
+ * await tx.isPersisted.promise
1366
1422
  *
1423
+ * @example
1367
1424
  * // Update with metadata
1368
- * 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
+ * }
1369
1440
  */
1441
+
1370
1442
  // Overload 1: Update multiple items with a callback
1371
1443
  update<TItem extends object = T>(
1372
1444
  key: Array<TKey | unknown>,
@@ -1533,8 +1605,11 @@ export class CollectionImpl<
1533
1605
  // Create a new transaction with a mutation function that calls the onUpdate handler
1534
1606
  const directOpTransaction = createTransaction<T>({
1535
1607
  mutationFn: async (params) => {
1536
- // Call the onUpdate handler with the transaction
1537
- 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
+ })
1538
1613
  },
1539
1614
  })
1540
1615
 
@@ -1552,18 +1627,33 @@ export class CollectionImpl<
1552
1627
 
1553
1628
  /**
1554
1629
  * Deletes one or more items from the collection
1555
- * @param ids - Single ID or array of IDs to delete
1630
+ * @param keys - Single key or array of keys to delete
1556
1631
  * @param config - Optional configuration including metadata
1557
- * @returns A TransactionType object representing the delete operation(s)
1632
+ * @returns A Transaction object representing the delete operation(s)
1558
1633
  * @example
1559
1634
  * // Delete a single item
1560
- * delete("todo-1")
1635
+ * const tx = collection.delete("todo-1")
1636
+ * await tx.isPersisted.promise
1561
1637
  *
1638
+ * @example
1562
1639
  * // Delete multiple items
1563
- * delete(["todo-1", "todo-2"])
1640
+ * const tx = collection.delete(["todo-1", "todo-2"])
1641
+ * await tx.isPersisted.promise
1564
1642
  *
1643
+ * @example
1565
1644
  * // Delete with metadata
1566
- * 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
+ * }
1567
1657
  */
1568
1658
  delete = (
1569
1659
  keys: Array<TKey> | TKey,
@@ -1629,8 +1719,11 @@ export class CollectionImpl<
1629
1719
  const directOpTransaction = createTransaction<T>({
1630
1720
  autoCommit: true,
1631
1721
  mutationFn: async (params) => {
1632
- // Call the onDelete handler with the transaction
1633
- 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
+ })
1634
1727
  },
1635
1728
  })
1636
1729
 
@@ -1646,8 +1739,19 @@ export class CollectionImpl<
1646
1739
 
1647
1740
  /**
1648
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`)
1746
+ *
1747
+ * for (const [key, item] of itemsMap) {
1748
+ * console.log(`${key}: ${item.title}`)
1749
+ * }
1649
1750
  *
1650
- * @returns A Map containing all items in the collection, with keys as identifiers
1751
+ * // Check if specific item exists
1752
+ * if (itemsMap.has("todo-1")) {
1753
+ * console.log("Todo 1 exists:", itemsMap.get("todo-1"))
1754
+ * }
1651
1755
  */
1652
1756
  get state() {
1653
1757
  const result = new Map<TKey, T>()
@@ -1720,8 +1824,24 @@ export class CollectionImpl<
1720
1824
 
1721
1825
  /**
1722
1826
  * Subscribe to changes in the collection
1723
- * @param callback - A function that will be called with the changes in the collection
1724
- * @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 })
1725
1845
  */
1726
1846
  public subscribeChanges(
1727
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,
@@ -162,7 +162,8 @@ export function liveQueryCollectionOptions<
162
162
 
163
163
  const allCollectionsReady = () => {
164
164
  return Object.values(collections).every(
165
- (collection) => collection.status === `ready`
165
+ (collection) =>
166
+ collection.status === `ready` || collection.status === `initialCommit`
166
167
  )
167
168
  }
168
169
 
@@ -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`