@tanstack/db 0.0.17 → 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.
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/collection.cjs +47 -6
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +150 -45
- package/dist/cjs/deferred.cjs.map +1 -1
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +116 -0
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +179 -0
- package/dist/cjs/types.d.cts +168 -12
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection.d.ts +150 -45
- package/dist/esm/collection.js +47 -6
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/deferred.js.map +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/transactions.d.ts +179 -0
- package/dist/esm/transactions.js +116 -0
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +168 -12
- package/package.json +1 -1
- package/src/collection.ts +165 -50
- package/src/proxy.ts +0 -1
- package/src/transactions.ts +179 -0
- package/src/types.ts +198 -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
|
-
* //
|
|
57
|
-
* const todos = createCollection
|
|
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
|
|
1247
|
-
* @returns A
|
|
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
|
|
1251
|
-
* insert({ text: "Buy
|
|
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
|
-
*
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1256
|
-
* { text: "
|
|
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
|
-
*
|
|
1260
|
-
*
|
|
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!(
|
|
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
|
|
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
|
|
1345
|
-
* update(todo, (draft) => {
|
|
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",
|
|
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!(
|
|
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
|
|
1630
|
+
* @param keys - Single key or array of keys to delete
|
|
1561
1631
|
* @param config - Optional configuration including metadata
|
|
1562
|
-
* @returns A
|
|
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!(
|
|
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
|
-
*
|
|
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 -
|
|
1729
|
-
* @
|
|
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,
|
package/src/transactions.ts
CHANGED
|
@@ -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`
|