mvcc-api 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,12 +34,27 @@ It easily and powerfully solves complex concurrency problems that are difficult
34
34
  * Business logic and storage logic can be perfectly separated.
35
35
 
36
36
  4. **Improved Development Productivity**
37
- * No need to write complex synchronization code yourself; write safe code with just intuitive `api.read()`, `api.write()`, and `commit()`.
37
+ * No need to write complex synchronization code yourself; write safe code with just intuitive `api.read()`, `api.write()`, and `api.commit()`.
38
38
 
39
39
  ## Installation
40
40
 
41
+ ### Node.js
42
+
41
43
  ```bash
42
44
  npm install mvcc-api
45
+ # or
46
+ npx jsr add @izure/mvcc-api
47
+ ```
48
+
49
+ ### Browser
50
+
51
+ ```javascript
52
+ import {
53
+ SyncMVCCStrategy,
54
+ SyncMVCCTransaction,
55
+ AsyncMVCCStrategy,
56
+ AsyncMVCCTransaction
57
+ } from 'https://cdn.jsdelivr.net/npm/mvcc-api@1/+esm'
43
58
  ```
44
59
 
45
60
  ## Usage
@@ -121,8 +136,9 @@ const child = parent.createNested()
121
136
  parent.write('shared', 'parent') // Parent modifies after child creation
122
137
  child.write('shared', 'child') // Child modifies same key
123
138
 
124
- const result = child.commit()
139
+ const result = child.commit('It should fail')
125
140
  if (!result.success) {
141
+ console.log(result.label) // "It should fail"
126
142
  console.log(result.error) // "Commit conflict: Key 'shared' was modified..."
127
143
  }
128
144
  ```
@@ -174,23 +190,26 @@ const bResult = b.commit()
174
190
 
175
191
  | Method | Description | Return Value |
176
192
  | :--- | :--- | :--- |
177
- | `create(key, value)` | Create new key-value | `this` |
178
- | `write(key, value)` | Update existing key | `this` |
179
- | `delete(key)` | Delete key | `this` |
180
- | `read(key)` | Read value | `T \| null` |
181
- | `exists(key)` | Check if key exists | `boolean` |
182
- | `commit()` | Apply changes | `TransactionResult<K, T>` |
183
- | `rollback()` | Discard changes | `TransactionResult<K, T>` |
193
+ | `create(key: K, value: T)` | Create new key-value | `this` |
194
+ | `write(key: K, value: T)` | Update existing key | `this` |
195
+ | `delete(key: K)` | Delete key | `this` |
196
+ | `read(key: K)` | Read value | `T \| null` |
197
+ | `exists(key: K)` | Check if key exists | `boolean` |
198
+ | `commit(label?: string)` | Apply changes | `TransactionResult<K, T>` |
199
+ | `rollback(label?: string)` | Discard changes | `TransactionResult<K, T>` |
184
200
  | `createNested()` | Create child transaction | `MVCCTransaction` |
185
201
 
186
202
  ### `TransactionResult<K, T>`
187
203
 
188
204
  ```typescript
189
205
  type TransactionEntry<K, T> = { key: K, data: T }
206
+ type TransactionConflict<K, T> = { key: K, parent: T, child: T }
190
207
 
191
208
  {
192
209
  success: boolean // Success status
210
+ label?: string // Label of the transaction
193
211
  error?: string // Error message on failure (e.g. conflict)
212
+ conflict?: TransactionConflict<K, T> // Conflict information on failure
194
213
  created: TransactionEntry[] // Keys and values created via create()
195
214
  updated: TransactionEntry[] // Keys and values updated via write()
196
215
  deleted: TransactionEntry[] // Keys deleted via delete() and their previous values
@@ -239,25 +239,57 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
239
239
  return this._diskRead(key, snapshotVersion);
240
240
  }
241
241
  }
242
- commit() {
242
+ commit(label) {
243
243
  const { created, updated, deleted } = this._getResultEntries();
244
244
  if (this.committed) {
245
- return { success: false, error: "Transaction already committed", created, updated, deleted };
245
+ return {
246
+ label,
247
+ success: false,
248
+ error: "Transaction already committed",
249
+ conflict: void 0,
250
+ created,
251
+ updated,
252
+ deleted
253
+ };
246
254
  }
247
255
  if (this.hasCommittedAncestor()) {
248
- return { success: false, error: "Ancestor transaction already committed", created, updated, deleted };
256
+ return {
257
+ label,
258
+ success: false,
259
+ error: "Ancestor transaction already committed",
260
+ conflict: void 0,
261
+ created,
262
+ updated,
263
+ deleted
264
+ };
249
265
  }
250
266
  if (this.parent) {
251
- const error = this.parent._merge(this);
252
- if (error) {
253
- return { success: false, error, created, updated, deleted };
267
+ const failure = this.parent._merge(this);
268
+ if (failure) {
269
+ return {
270
+ label,
271
+ success: false,
272
+ error: failure.error,
273
+ conflict: failure.conflict,
274
+ created,
275
+ updated,
276
+ deleted
277
+ };
254
278
  }
255
279
  this.committed = true;
256
280
  } else {
257
281
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
258
- const error = this._merge(this);
259
- if (error) {
260
- return { success: false, error, created: [], updated: [], deleted: [] };
282
+ const failure = this._merge(this);
283
+ if (failure) {
284
+ return {
285
+ label,
286
+ success: false,
287
+ error: failure.error,
288
+ conflict: failure.conflict,
289
+ created: [],
290
+ updated: [],
291
+ deleted: []
292
+ };
261
293
  }
262
294
  this.writeBuffer.clear();
263
295
  this.deleteBuffer.clear();
@@ -268,20 +300,40 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
268
300
  this.localVersion = 0;
269
301
  }
270
302
  }
271
- return { success: true, created, updated, deleted };
303
+ return {
304
+ label,
305
+ success: true,
306
+ created,
307
+ updated,
308
+ deleted
309
+ };
272
310
  }
273
311
  _merge(child) {
274
312
  if (this.parent) {
275
313
  for (const key of child.writeBuffer.keys()) {
276
314
  const lastModLocalVer = this.keyVersions.get(key);
277
315
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
278
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
316
+ return {
317
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
318
+ conflict: {
319
+ key,
320
+ parent: this.read(key),
321
+ child: child.read(key)
322
+ }
323
+ };
279
324
  }
280
325
  }
281
326
  for (const key of child.deleteBuffer) {
282
327
  const lastModLocalVer = this.keyVersions.get(key);
283
328
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
284
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
329
+ return {
330
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
331
+ conflict: {
332
+ key,
333
+ parent: this.read(key),
334
+ child: child.read(key)
335
+ }
336
+ };
285
337
  }
286
338
  }
287
339
  const newLocalVersion = this.localVersion + 1;
@@ -309,18 +361,45 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
309
361
  this.localVersion = newLocalVersion;
310
362
  this.root.activeTransactions.delete(child);
311
363
  } else {
312
- this.root.activeTransactions.delete(child);
313
364
  const newVersion = this.version + 1;
314
- const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
315
- for (const key of modifiedKeys) {
316
- const versions = this.versionIndex.get(key);
317
- if (versions && versions.length > 0) {
318
- const lastVer = versions[versions.length - 1].version;
319
- if (lastVer > child.snapshotVersion) {
320
- return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
365
+ if (child !== this) {
366
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
367
+ for (const key of modifiedKeys) {
368
+ const versions = this.versionIndex.get(key);
369
+ if (versions && versions.length > 0) {
370
+ const lastVer = versions[versions.length - 1].version;
371
+ if (lastVer > child.snapshotVersion) {
372
+ return {
373
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
374
+ conflict: {
375
+ key,
376
+ parent: this.read(key),
377
+ child: child.read(key)
378
+ }
379
+ };
380
+ }
321
381
  }
322
382
  }
323
383
  }
384
+ for (const [key, value] of child.writeBuffer) {
385
+ this.writeBuffer.set(key, value);
386
+ this.deleteBuffer.delete(key);
387
+ if (child.createdKeys.has(key)) {
388
+ this.createdKeys.add(key);
389
+ }
390
+ }
391
+ for (const key of child.deleteBuffer) {
392
+ this.deleteBuffer.add(key);
393
+ this.writeBuffer.delete(key);
394
+ this.createdKeys.delete(key);
395
+ const deletedValue = child.deletedValues.get(key);
396
+ if (deletedValue !== void 0) {
397
+ this.deletedValues.set(key, deletedValue);
398
+ }
399
+ if (child.originallyExisted.has(key)) {
400
+ this.originallyExisted.add(key);
401
+ }
402
+ }
324
403
  for (const [key, value] of child.writeBuffer) {
325
404
  this._diskWrite(key, value, newVersion);
326
405
  }
@@ -328,6 +407,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
328
407
  this._diskDelete(key, newVersion);
329
408
  }
330
409
  this.version = newVersion;
410
+ this.root.activeTransactions.delete(child);
331
411
  this._cleanupDeletedCache();
332
412
  }
333
413
  return null;
@@ -365,7 +445,17 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
365
445
  break;
366
446
  }
367
447
  }
368
- if (!targetVerObj || !targetVerObj.exists) return null;
448
+ if (!targetVerObj) {
449
+ if (nextVerObj) {
450
+ const cached2 = this.deletedCache.get(key);
451
+ if (cached2) {
452
+ const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
453
+ if (match) return match.value;
454
+ }
455
+ }
456
+ return null;
457
+ }
458
+ if (!targetVerObj.exists) return null;
369
459
  if (!nextVerObj) {
370
460
  return strategy.read(key);
371
461
  }
@@ -778,38 +868,74 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
778
868
  return this._diskRead(key, snapshotVersion);
779
869
  }
780
870
  }
781
- async commit() {
782
- return this.writeLock(async () => {
783
- const { created, updated, deleted } = this._getResultEntries();
784
- if (this.committed) {
785
- return { success: false, error: "Transaction already committed", created, updated, deleted };
786
- }
787
- if (this.hasCommittedAncestor()) {
788
- return { success: false, error: "Ancestor transaction already committed", created, updated, deleted };
871
+ async commit(label) {
872
+ const { created, updated, deleted } = this._getResultEntries();
873
+ if (this.committed) {
874
+ return {
875
+ label,
876
+ success: false,
877
+ error: "Transaction already committed",
878
+ conflict: void 0,
879
+ created,
880
+ updated,
881
+ deleted
882
+ };
883
+ }
884
+ if (this.hasCommittedAncestor()) {
885
+ return {
886
+ label,
887
+ success: false,
888
+ error: "Ancestor transaction already committed",
889
+ conflict: void 0,
890
+ created,
891
+ updated,
892
+ deleted
893
+ };
894
+ }
895
+ if (this.parent) {
896
+ const failure = await this.parent._merge(this);
897
+ if (failure) {
898
+ return {
899
+ label,
900
+ success: false,
901
+ error: failure.error,
902
+ conflict: failure.conflict,
903
+ created,
904
+ updated,
905
+ deleted
906
+ };
789
907
  }
790
- if (this.parent) {
791
- const error = await this.parent._merge(this);
792
- if (error) {
793
- return { success: false, error, created, updated, deleted };
794
- }
795
- this.committed = true;
796
- } else {
797
- if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
798
- const error = await this._merge(this);
799
- if (error) {
800
- return { success: false, error, created: [], updated: [], deleted: [] };
801
- }
802
- this.writeBuffer.clear();
803
- this.deleteBuffer.clear();
804
- this.createdKeys.clear();
805
- this.deletedValues.clear();
806
- this.originallyExisted.clear();
807
- this.keyVersions.clear();
808
- this.localVersion = 0;
908
+ this.committed = true;
909
+ } else {
910
+ if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
911
+ const failure = await this._merge(this);
912
+ if (failure) {
913
+ return {
914
+ label,
915
+ success: false,
916
+ error: failure.error,
917
+ conflict: failure.conflict,
918
+ created: [],
919
+ updated: [],
920
+ deleted: []
921
+ };
809
922
  }
923
+ this.writeBuffer.clear();
924
+ this.deleteBuffer.clear();
925
+ this.createdKeys.clear();
926
+ this.deletedValues.clear();
927
+ this.originallyExisted.clear();
928
+ this.keyVersions.clear();
929
+ this.localVersion = 0;
810
930
  }
811
- return { success: true, created, updated, deleted };
812
- });
931
+ }
932
+ return {
933
+ label,
934
+ success: true,
935
+ created,
936
+ updated,
937
+ deleted
938
+ };
813
939
  }
814
940
  async _merge(child) {
815
941
  return this.writeLock(async () => {
@@ -817,13 +943,27 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
817
943
  for (const key of child.writeBuffer.keys()) {
818
944
  const lastModLocalVer = this.keyVersions.get(key);
819
945
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
820
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
946
+ return {
947
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
948
+ conflict: {
949
+ key,
950
+ parent: await this.read(key),
951
+ child: await child.read(key)
952
+ }
953
+ };
821
954
  }
822
955
  }
823
956
  for (const key of child.deleteBuffer) {
824
957
  const lastModLocalVer = this.keyVersions.get(key);
825
958
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
826
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
959
+ return {
960
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
961
+ conflict: {
962
+ key,
963
+ parent: await this.read(key),
964
+ child: await child.read(key)
965
+ }
966
+ };
827
967
  }
828
968
  }
829
969
  const newLocalVersion = this.localVersion + 1;
@@ -850,19 +990,47 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
850
990
  }
851
991
  this.localVersion = newLocalVersion;
852
992
  this.root.activeTransactions.delete(child);
993
+ return null;
853
994
  } else {
854
- this.root.activeTransactions.delete(child);
855
995
  const newVersion = this.version + 1;
856
- const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
857
- for (const key of modifiedKeys) {
858
- const versions = this.versionIndex.get(key);
859
- if (versions && versions.length > 0) {
860
- const lastVer = versions[versions.length - 1].version;
861
- if (lastVer > child.snapshotVersion) {
862
- return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
996
+ if (child !== this) {
997
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
998
+ for (const key of modifiedKeys) {
999
+ const versions = this.versionIndex.get(key);
1000
+ if (versions && versions.length > 0) {
1001
+ const lastVer = versions[versions.length - 1].version;
1002
+ if (lastVer > child.snapshotVersion) {
1003
+ return {
1004
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
1005
+ conflict: {
1006
+ key,
1007
+ parent: await this.read(key),
1008
+ child: await child.read(key)
1009
+ }
1010
+ };
1011
+ }
863
1012
  }
864
1013
  }
865
1014
  }
1015
+ for (const [key, value] of child.writeBuffer) {
1016
+ this.writeBuffer.set(key, value);
1017
+ this.deleteBuffer.delete(key);
1018
+ if (child.createdKeys.has(key)) {
1019
+ this.createdKeys.add(key);
1020
+ }
1021
+ }
1022
+ for (const key of child.deleteBuffer) {
1023
+ this.deleteBuffer.add(key);
1024
+ this.writeBuffer.delete(key);
1025
+ this.createdKeys.delete(key);
1026
+ const deletedValue = child.deletedValues.get(key);
1027
+ if (deletedValue !== void 0) {
1028
+ this.deletedValues.set(key, deletedValue);
1029
+ }
1030
+ if (child.originallyExisted.has(key)) {
1031
+ this.originallyExisted.add(key);
1032
+ }
1033
+ }
866
1034
  for (const [key, value] of child.writeBuffer) {
867
1035
  await this._diskWrite(key, value, newVersion);
868
1036
  }
@@ -870,9 +1038,10 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
870
1038
  await this._diskDelete(key, newVersion);
871
1039
  }
872
1040
  this.version = newVersion;
1041
+ this.root.activeTransactions.delete(child);
873
1042
  this._cleanupDeletedCache();
1043
+ return null;
874
1044
  }
875
- return null;
876
1045
  });
877
1046
  }
878
1047
  // --- Internal IO Helpers (Root Only) ---
@@ -908,7 +1077,17 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
908
1077
  break;
909
1078
  }
910
1079
  }
911
- if (!targetVerObj || !targetVerObj.exists) return null;
1080
+ if (!targetVerObj) {
1081
+ if (nextVerObj) {
1082
+ const cached2 = this.deletedCache.get(key);
1083
+ if (cached2) {
1084
+ const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
1085
+ if (match) return match.value;
1086
+ }
1087
+ }
1088
+ return null;
1089
+ }
1090
+ if (!targetVerObj.exists) return null;
912
1091
  if (!nextVerObj) {
913
1092
  return strategy.read(key);
914
1093
  }
@@ -210,25 +210,57 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
210
210
  return this._diskRead(key, snapshotVersion);
211
211
  }
212
212
  }
213
- commit() {
213
+ commit(label) {
214
214
  const { created, updated, deleted } = this._getResultEntries();
215
215
  if (this.committed) {
216
- return { success: false, error: "Transaction already committed", created, updated, deleted };
216
+ return {
217
+ label,
218
+ success: false,
219
+ error: "Transaction already committed",
220
+ conflict: void 0,
221
+ created,
222
+ updated,
223
+ deleted
224
+ };
217
225
  }
218
226
  if (this.hasCommittedAncestor()) {
219
- return { success: false, error: "Ancestor transaction already committed", created, updated, deleted };
227
+ return {
228
+ label,
229
+ success: false,
230
+ error: "Ancestor transaction already committed",
231
+ conflict: void 0,
232
+ created,
233
+ updated,
234
+ deleted
235
+ };
220
236
  }
221
237
  if (this.parent) {
222
- const error = this.parent._merge(this);
223
- if (error) {
224
- return { success: false, error, created, updated, deleted };
238
+ const failure = this.parent._merge(this);
239
+ if (failure) {
240
+ return {
241
+ label,
242
+ success: false,
243
+ error: failure.error,
244
+ conflict: failure.conflict,
245
+ created,
246
+ updated,
247
+ deleted
248
+ };
225
249
  }
226
250
  this.committed = true;
227
251
  } else {
228
252
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
229
- const error = this._merge(this);
230
- if (error) {
231
- return { success: false, error, created: [], updated: [], deleted: [] };
253
+ const failure = this._merge(this);
254
+ if (failure) {
255
+ return {
256
+ label,
257
+ success: false,
258
+ error: failure.error,
259
+ conflict: failure.conflict,
260
+ created: [],
261
+ updated: [],
262
+ deleted: []
263
+ };
232
264
  }
233
265
  this.writeBuffer.clear();
234
266
  this.deleteBuffer.clear();
@@ -239,20 +271,40 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
239
271
  this.localVersion = 0;
240
272
  }
241
273
  }
242
- return { success: true, created, updated, deleted };
274
+ return {
275
+ label,
276
+ success: true,
277
+ created,
278
+ updated,
279
+ deleted
280
+ };
243
281
  }
244
282
  _merge(child) {
245
283
  if (this.parent) {
246
284
  for (const key of child.writeBuffer.keys()) {
247
285
  const lastModLocalVer = this.keyVersions.get(key);
248
286
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
249
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
287
+ return {
288
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
289
+ conflict: {
290
+ key,
291
+ parent: this.read(key),
292
+ child: child.read(key)
293
+ }
294
+ };
250
295
  }
251
296
  }
252
297
  for (const key of child.deleteBuffer) {
253
298
  const lastModLocalVer = this.keyVersions.get(key);
254
299
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
255
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
300
+ return {
301
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
302
+ conflict: {
303
+ key,
304
+ parent: this.read(key),
305
+ child: child.read(key)
306
+ }
307
+ };
256
308
  }
257
309
  }
258
310
  const newLocalVersion = this.localVersion + 1;
@@ -280,18 +332,45 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
280
332
  this.localVersion = newLocalVersion;
281
333
  this.root.activeTransactions.delete(child);
282
334
  } else {
283
- this.root.activeTransactions.delete(child);
284
335
  const newVersion = this.version + 1;
285
- const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
286
- for (const key of modifiedKeys) {
287
- const versions = this.versionIndex.get(key);
288
- if (versions && versions.length > 0) {
289
- const lastVer = versions[versions.length - 1].version;
290
- if (lastVer > child.snapshotVersion) {
291
- return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
336
+ if (child !== this) {
337
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
338
+ for (const key of modifiedKeys) {
339
+ const versions = this.versionIndex.get(key);
340
+ if (versions && versions.length > 0) {
341
+ const lastVer = versions[versions.length - 1].version;
342
+ if (lastVer > child.snapshotVersion) {
343
+ return {
344
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
345
+ conflict: {
346
+ key,
347
+ parent: this.read(key),
348
+ child: child.read(key)
349
+ }
350
+ };
351
+ }
292
352
  }
293
353
  }
294
354
  }
355
+ for (const [key, value] of child.writeBuffer) {
356
+ this.writeBuffer.set(key, value);
357
+ this.deleteBuffer.delete(key);
358
+ if (child.createdKeys.has(key)) {
359
+ this.createdKeys.add(key);
360
+ }
361
+ }
362
+ for (const key of child.deleteBuffer) {
363
+ this.deleteBuffer.add(key);
364
+ this.writeBuffer.delete(key);
365
+ this.createdKeys.delete(key);
366
+ const deletedValue = child.deletedValues.get(key);
367
+ if (deletedValue !== void 0) {
368
+ this.deletedValues.set(key, deletedValue);
369
+ }
370
+ if (child.originallyExisted.has(key)) {
371
+ this.originallyExisted.add(key);
372
+ }
373
+ }
295
374
  for (const [key, value] of child.writeBuffer) {
296
375
  this._diskWrite(key, value, newVersion);
297
376
  }
@@ -299,6 +378,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
299
378
  this._diskDelete(key, newVersion);
300
379
  }
301
380
  this.version = newVersion;
381
+ this.root.activeTransactions.delete(child);
302
382
  this._cleanupDeletedCache();
303
383
  }
304
384
  return null;
@@ -336,7 +416,17 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
336
416
  break;
337
417
  }
338
418
  }
339
- if (!targetVerObj || !targetVerObj.exists) return null;
419
+ if (!targetVerObj) {
420
+ if (nextVerObj) {
421
+ const cached2 = this.deletedCache.get(key);
422
+ if (cached2) {
423
+ const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
424
+ if (match) return match.value;
425
+ }
426
+ }
427
+ return null;
428
+ }
429
+ if (!targetVerObj.exists) return null;
340
430
  if (!nextVerObj) {
341
431
  return strategy.read(key);
342
432
  }
@@ -749,38 +839,74 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
749
839
  return this._diskRead(key, snapshotVersion);
750
840
  }
751
841
  }
752
- async commit() {
753
- return this.writeLock(async () => {
754
- const { created, updated, deleted } = this._getResultEntries();
755
- if (this.committed) {
756
- return { success: false, error: "Transaction already committed", created, updated, deleted };
757
- }
758
- if (this.hasCommittedAncestor()) {
759
- return { success: false, error: "Ancestor transaction already committed", created, updated, deleted };
842
+ async commit(label) {
843
+ const { created, updated, deleted } = this._getResultEntries();
844
+ if (this.committed) {
845
+ return {
846
+ label,
847
+ success: false,
848
+ error: "Transaction already committed",
849
+ conflict: void 0,
850
+ created,
851
+ updated,
852
+ deleted
853
+ };
854
+ }
855
+ if (this.hasCommittedAncestor()) {
856
+ return {
857
+ label,
858
+ success: false,
859
+ error: "Ancestor transaction already committed",
860
+ conflict: void 0,
861
+ created,
862
+ updated,
863
+ deleted
864
+ };
865
+ }
866
+ if (this.parent) {
867
+ const failure = await this.parent._merge(this);
868
+ if (failure) {
869
+ return {
870
+ label,
871
+ success: false,
872
+ error: failure.error,
873
+ conflict: failure.conflict,
874
+ created,
875
+ updated,
876
+ deleted
877
+ };
760
878
  }
761
- if (this.parent) {
762
- const error = await this.parent._merge(this);
763
- if (error) {
764
- return { success: false, error, created, updated, deleted };
765
- }
766
- this.committed = true;
767
- } else {
768
- if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
769
- const error = await this._merge(this);
770
- if (error) {
771
- return { success: false, error, created: [], updated: [], deleted: [] };
772
- }
773
- this.writeBuffer.clear();
774
- this.deleteBuffer.clear();
775
- this.createdKeys.clear();
776
- this.deletedValues.clear();
777
- this.originallyExisted.clear();
778
- this.keyVersions.clear();
779
- this.localVersion = 0;
879
+ this.committed = true;
880
+ } else {
881
+ if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
882
+ const failure = await this._merge(this);
883
+ if (failure) {
884
+ return {
885
+ label,
886
+ success: false,
887
+ error: failure.error,
888
+ conflict: failure.conflict,
889
+ created: [],
890
+ updated: [],
891
+ deleted: []
892
+ };
780
893
  }
894
+ this.writeBuffer.clear();
895
+ this.deleteBuffer.clear();
896
+ this.createdKeys.clear();
897
+ this.deletedValues.clear();
898
+ this.originallyExisted.clear();
899
+ this.keyVersions.clear();
900
+ this.localVersion = 0;
781
901
  }
782
- return { success: true, created, updated, deleted };
783
- });
902
+ }
903
+ return {
904
+ label,
905
+ success: true,
906
+ created,
907
+ updated,
908
+ deleted
909
+ };
784
910
  }
785
911
  async _merge(child) {
786
912
  return this.writeLock(async () => {
@@ -788,13 +914,27 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
788
914
  for (const key of child.writeBuffer.keys()) {
789
915
  const lastModLocalVer = this.keyVersions.get(key);
790
916
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
791
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
917
+ return {
918
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
919
+ conflict: {
920
+ key,
921
+ parent: await this.read(key),
922
+ child: await child.read(key)
923
+ }
924
+ };
792
925
  }
793
926
  }
794
927
  for (const key of child.deleteBuffer) {
795
928
  const lastModLocalVer = this.keyVersions.get(key);
796
929
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
797
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
930
+ return {
931
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
932
+ conflict: {
933
+ key,
934
+ parent: await this.read(key),
935
+ child: await child.read(key)
936
+ }
937
+ };
798
938
  }
799
939
  }
800
940
  const newLocalVersion = this.localVersion + 1;
@@ -821,19 +961,47 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
821
961
  }
822
962
  this.localVersion = newLocalVersion;
823
963
  this.root.activeTransactions.delete(child);
964
+ return null;
824
965
  } else {
825
- this.root.activeTransactions.delete(child);
826
966
  const newVersion = this.version + 1;
827
- const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
828
- for (const key of modifiedKeys) {
829
- const versions = this.versionIndex.get(key);
830
- if (versions && versions.length > 0) {
831
- const lastVer = versions[versions.length - 1].version;
832
- if (lastVer > child.snapshotVersion) {
833
- return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
967
+ if (child !== this) {
968
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
969
+ for (const key of modifiedKeys) {
970
+ const versions = this.versionIndex.get(key);
971
+ if (versions && versions.length > 0) {
972
+ const lastVer = versions[versions.length - 1].version;
973
+ if (lastVer > child.snapshotVersion) {
974
+ return {
975
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
976
+ conflict: {
977
+ key,
978
+ parent: await this.read(key),
979
+ child: await child.read(key)
980
+ }
981
+ };
982
+ }
834
983
  }
835
984
  }
836
985
  }
986
+ for (const [key, value] of child.writeBuffer) {
987
+ this.writeBuffer.set(key, value);
988
+ this.deleteBuffer.delete(key);
989
+ if (child.createdKeys.has(key)) {
990
+ this.createdKeys.add(key);
991
+ }
992
+ }
993
+ for (const key of child.deleteBuffer) {
994
+ this.deleteBuffer.add(key);
995
+ this.writeBuffer.delete(key);
996
+ this.createdKeys.delete(key);
997
+ const deletedValue = child.deletedValues.get(key);
998
+ if (deletedValue !== void 0) {
999
+ this.deletedValues.set(key, deletedValue);
1000
+ }
1001
+ if (child.originallyExisted.has(key)) {
1002
+ this.originallyExisted.add(key);
1003
+ }
1004
+ }
837
1005
  for (const [key, value] of child.writeBuffer) {
838
1006
  await this._diskWrite(key, value, newVersion);
839
1007
  }
@@ -841,9 +1009,10 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
841
1009
  await this._diskDelete(key, newVersion);
842
1010
  }
843
1011
  this.version = newVersion;
1012
+ this.root.activeTransactions.delete(child);
844
1013
  this._cleanupDeletedCache();
1014
+ return null;
845
1015
  }
846
- return null;
847
1016
  });
848
1017
  }
849
1018
  // --- Internal IO Helpers (Root Only) ---
@@ -879,7 +1048,17 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
879
1048
  break;
880
1049
  }
881
1050
  }
882
- if (!targetVerObj || !targetVerObj.exists) return null;
1051
+ if (!targetVerObj) {
1052
+ if (nextVerObj) {
1053
+ const cached2 = this.deletedCache.get(key);
1054
+ if (cached2) {
1055
+ const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
1056
+ if (match) return match.value;
1057
+ }
1058
+ }
1059
+ return null;
1060
+ }
1061
+ if (!targetVerObj.exists) return null;
883
1062
  if (!nextVerObj) {
884
1063
  return strategy.read(key);
885
1064
  }
@@ -1,5 +1,5 @@
1
1
  import type { AsyncMVCCStrategy } from './Strategy';
2
- import type { TransactionResult } from '../../types';
2
+ import type { TransactionMergeFailure, TransactionResult } from '../../types';
3
3
  import { MVCCTransaction } from '../base';
4
4
  export declare class AsyncMVCCTransaction<S extends AsyncMVCCStrategy<K, T>, K, T> extends MVCCTransaction<S, K, T> {
5
5
  private lock;
@@ -11,8 +11,8 @@ export declare class AsyncMVCCTransaction<S extends AsyncMVCCStrategy<K, T>, K,
11
11
  read(key: K): Promise<T | null>;
12
12
  exists(key: K): Promise<boolean>;
13
13
  _readSnapshot(key: K, snapshotVersion: number, snapshotLocalVersion?: number): Promise<T | null>;
14
- commit(): Promise<TransactionResult<K, T>>;
15
- _merge(child: MVCCTransaction<S, K, T>): Promise<string | null>;
14
+ commit(label?: string): Promise<TransactionResult<K, T>>;
15
+ _merge(child: MVCCTransaction<S, K, T>): Promise<TransactionMergeFailure<K, T> | null>;
16
16
  _diskWrite(key: K, value: T, version: number): Promise<void>;
17
17
  _diskRead(key: K, snapshotVersion: number): Promise<T | null>;
18
18
  _diskExists(key: K, snapshotVersion: number): Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import type { Deferred, TransactionResult, TransactionEntry } from '../../types';
1
+ import type { Deferred, TransactionResult, TransactionEntry, TransactionMergeFailure } from '../../types';
2
2
  import type { MVCCStrategy } from './Strategy';
3
3
  /**
4
4
  * MVCC Transaction abstract class.
@@ -93,9 +93,10 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
93
93
  * Commits the transaction.
94
94
  * If root, persists to storage.
95
95
  * If nested, merges to parent.
96
+ * @param label The label for the commit.
96
97
  * @returns The result object with success, created, and obsolete keys.
97
98
  */
98
- abstract commit(): Deferred<TransactionResult<K, T>>;
99
+ abstract commit(label?: string): Deferred<TransactionResult<K, T>>;
99
100
  /**
100
101
  * Creates a nested transaction (child) from this transaction.
101
102
  * @returns A new nested transaction instance.
@@ -106,7 +107,7 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
106
107
  * @param child The committed child transaction.
107
108
  * @returns Error message if conflict, null if success.
108
109
  */
109
- abstract _merge(child: MVCCTransaction<S, K, T>): Deferred<string | null>;
110
+ abstract _merge(child: MVCCTransaction<S, K, T>): Deferred<TransactionMergeFailure<K, T> | null>;
110
111
  /**
111
112
  * Reads a value at a specific snapshot version.
112
113
  * Used by child transactions to read from parent respecting the child's snapshot.
@@ -1,5 +1,5 @@
1
1
  import type { SyncMVCCStrategy } from './Strategy';
2
- import type { TransactionResult } from '../../types';
2
+ import type { TransactionResult, TransactionMergeFailure } from '../../types';
3
3
  import { MVCCTransaction } from '../base';
4
4
  export declare class SyncMVCCTransaction<S extends SyncMVCCStrategy<K, T>, K, T> extends MVCCTransaction<S, K, T> {
5
5
  create(key: K, value: T): this;
@@ -9,8 +9,8 @@ export declare class SyncMVCCTransaction<S extends SyncMVCCStrategy<K, T>, K, T>
9
9
  read(key: K): T | null;
10
10
  exists(key: K): boolean;
11
11
  _readSnapshot(key: K, snapshotVersion: number, snapshotLocalVersion?: number): T | null;
12
- commit(): TransactionResult<K, T>;
13
- _merge(child: MVCCTransaction<S, K, T>): string | null;
12
+ commit(label?: string): TransactionResult<K, T>;
13
+ _merge(child: SyncMVCCTransaction<S, K, T>): TransactionMergeFailure<K, T> | null;
14
14
  _diskWrite(key: K, value: T, version: number): void;
15
15
  _diskRead(key: K, snapshotVersion: number): T | null;
16
16
  _diskExists(key: K, snapshotVersion: number): boolean;
@@ -7,9 +7,26 @@ export type TransactionEntry<K, T> = {
7
7
  key: K;
8
8
  data: T;
9
9
  };
10
+ export type TransactionConflict<K, T> = {
11
+ key: K;
12
+ parent: T;
13
+ child: T;
14
+ };
15
+ export type TransactionMergeFailure<K, T> = {
16
+ error: string;
17
+ conflict: TransactionConflict<K, T>;
18
+ };
10
19
  export type TransactionResult<K, T> = {
11
- success: boolean;
12
- error?: string;
20
+ label?: string;
21
+ success: true;
22
+ created: TransactionEntry<K, T>[];
23
+ updated: TransactionEntry<K, T>[];
24
+ deleted: TransactionEntry<K, T>[];
25
+ } | {
26
+ label?: string;
27
+ success: false;
28
+ error: string;
29
+ conflict?: TransactionConflict<K, T>;
13
30
  created: TransactionEntry<K, T>[];
14
31
  updated: TransactionEntry<K, T>[];
15
32
  deleted: TransactionEntry<K, T>[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mvcc-api",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Multiversion Concurrency Control (MVCC) API for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",