mvcc-api 1.2.3 → 1.2.4

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
@@ -38,8 +38,23 @@ It easily and powerfully solves complex concurrency problems that are difficult
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@7/+esm'
43
58
  ```
44
59
 
45
60
  ## Usage
@@ -187,10 +202,12 @@ const bResult = b.commit()
187
202
 
188
203
  ```typescript
189
204
  type TransactionEntry<K, T> = { key: K, data: T }
205
+ type TransactionConflict<K, T> = { key: K, parent: T, child: T }
190
206
 
191
207
  {
192
208
  success: boolean // Success status
193
209
  error?: string // Error message on failure (e.g. conflict)
210
+ conflict?: TransactionConflict<K, T> // Conflict information on failure
194
211
  created: TransactionEntry[] // Keys and values created via create()
195
212
  updated: TransactionEntry[] // Keys and values updated via write()
196
213
  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;
@@ -778,38 +858,74 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
778
858
  return this._diskRead(key, snapshotVersion);
779
859
  }
780
860
  }
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 };
861
+ async commit(label) {
862
+ const { created, updated, deleted } = this._getResultEntries();
863
+ if (this.committed) {
864
+ return {
865
+ label,
866
+ success: false,
867
+ error: "Transaction already committed",
868
+ conflict: void 0,
869
+ created,
870
+ updated,
871
+ deleted
872
+ };
873
+ }
874
+ if (this.hasCommittedAncestor()) {
875
+ return {
876
+ label,
877
+ success: false,
878
+ error: "Ancestor transaction already committed",
879
+ conflict: void 0,
880
+ created,
881
+ updated,
882
+ deleted
883
+ };
884
+ }
885
+ if (this.parent) {
886
+ const failure = await this.parent._merge(this);
887
+ if (failure) {
888
+ return {
889
+ label,
890
+ success: false,
891
+ error: failure.error,
892
+ conflict: failure.conflict,
893
+ created,
894
+ updated,
895
+ deleted
896
+ };
789
897
  }
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;
898
+ this.committed = true;
899
+ } else {
900
+ if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
901
+ const failure = await this._merge(this);
902
+ if (failure) {
903
+ return {
904
+ label,
905
+ success: false,
906
+ error: failure.error,
907
+ conflict: failure.conflict,
908
+ created: [],
909
+ updated: [],
910
+ deleted: []
911
+ };
809
912
  }
913
+ this.writeBuffer.clear();
914
+ this.deleteBuffer.clear();
915
+ this.createdKeys.clear();
916
+ this.deletedValues.clear();
917
+ this.originallyExisted.clear();
918
+ this.keyVersions.clear();
919
+ this.localVersion = 0;
810
920
  }
811
- return { success: true, created, updated, deleted };
812
- });
921
+ }
922
+ return {
923
+ label,
924
+ success: true,
925
+ created,
926
+ updated,
927
+ deleted
928
+ };
813
929
  }
814
930
  async _merge(child) {
815
931
  return this.writeLock(async () => {
@@ -817,13 +933,27 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
817
933
  for (const key of child.writeBuffer.keys()) {
818
934
  const lastModLocalVer = this.keyVersions.get(key);
819
935
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
820
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
936
+ return {
937
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
938
+ conflict: {
939
+ key,
940
+ parent: await this.read(key),
941
+ child: await child.read(key)
942
+ }
943
+ };
821
944
  }
822
945
  }
823
946
  for (const key of child.deleteBuffer) {
824
947
  const lastModLocalVer = this.keyVersions.get(key);
825
948
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
826
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
949
+ return {
950
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
951
+ conflict: {
952
+ key,
953
+ parent: await this.read(key),
954
+ child: await child.read(key)
955
+ }
956
+ };
827
957
  }
828
958
  }
829
959
  const newLocalVersion = this.localVersion + 1;
@@ -850,19 +980,47 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
850
980
  }
851
981
  this.localVersion = newLocalVersion;
852
982
  this.root.activeTransactions.delete(child);
983
+ return null;
853
984
  } else {
854
- this.root.activeTransactions.delete(child);
855
985
  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})`;
986
+ if (child !== this) {
987
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
988
+ for (const key of modifiedKeys) {
989
+ const versions = this.versionIndex.get(key);
990
+ if (versions && versions.length > 0) {
991
+ const lastVer = versions[versions.length - 1].version;
992
+ if (lastVer > child.snapshotVersion) {
993
+ return {
994
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
995
+ conflict: {
996
+ key,
997
+ parent: await this.read(key),
998
+ child: await child.read(key)
999
+ }
1000
+ };
1001
+ }
863
1002
  }
864
1003
  }
865
1004
  }
1005
+ for (const [key, value] of child.writeBuffer) {
1006
+ this.writeBuffer.set(key, value);
1007
+ this.deleteBuffer.delete(key);
1008
+ if (child.createdKeys.has(key)) {
1009
+ this.createdKeys.add(key);
1010
+ }
1011
+ }
1012
+ for (const key of child.deleteBuffer) {
1013
+ this.deleteBuffer.add(key);
1014
+ this.writeBuffer.delete(key);
1015
+ this.createdKeys.delete(key);
1016
+ const deletedValue = child.deletedValues.get(key);
1017
+ if (deletedValue !== void 0) {
1018
+ this.deletedValues.set(key, deletedValue);
1019
+ }
1020
+ if (child.originallyExisted.has(key)) {
1021
+ this.originallyExisted.add(key);
1022
+ }
1023
+ }
866
1024
  for (const [key, value] of child.writeBuffer) {
867
1025
  await this._diskWrite(key, value, newVersion);
868
1026
  }
@@ -870,9 +1028,10 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
870
1028
  await this._diskDelete(key, newVersion);
871
1029
  }
872
1030
  this.version = newVersion;
1031
+ this.root.activeTransactions.delete(child);
873
1032
  this._cleanupDeletedCache();
1033
+ return null;
874
1034
  }
875
- return null;
876
1035
  });
877
1036
  }
878
1037
  // --- Internal IO Helpers (Root Only) ---
@@ -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;
@@ -749,38 +829,74 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
749
829
  return this._diskRead(key, snapshotVersion);
750
830
  }
751
831
  }
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 };
832
+ async commit(label) {
833
+ const { created, updated, deleted } = this._getResultEntries();
834
+ if (this.committed) {
835
+ return {
836
+ label,
837
+ success: false,
838
+ error: "Transaction already committed",
839
+ conflict: void 0,
840
+ created,
841
+ updated,
842
+ deleted
843
+ };
844
+ }
845
+ if (this.hasCommittedAncestor()) {
846
+ return {
847
+ label,
848
+ success: false,
849
+ error: "Ancestor transaction already committed",
850
+ conflict: void 0,
851
+ created,
852
+ updated,
853
+ deleted
854
+ };
855
+ }
856
+ if (this.parent) {
857
+ const failure = await this.parent._merge(this);
858
+ if (failure) {
859
+ return {
860
+ label,
861
+ success: false,
862
+ error: failure.error,
863
+ conflict: failure.conflict,
864
+ created,
865
+ updated,
866
+ deleted
867
+ };
760
868
  }
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;
869
+ this.committed = true;
870
+ } else {
871
+ if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
872
+ const failure = await this._merge(this);
873
+ if (failure) {
874
+ return {
875
+ label,
876
+ success: false,
877
+ error: failure.error,
878
+ conflict: failure.conflict,
879
+ created: [],
880
+ updated: [],
881
+ deleted: []
882
+ };
780
883
  }
884
+ this.writeBuffer.clear();
885
+ this.deleteBuffer.clear();
886
+ this.createdKeys.clear();
887
+ this.deletedValues.clear();
888
+ this.originallyExisted.clear();
889
+ this.keyVersions.clear();
890
+ this.localVersion = 0;
781
891
  }
782
- return { success: true, created, updated, deleted };
783
- });
892
+ }
893
+ return {
894
+ label,
895
+ success: true,
896
+ created,
897
+ updated,
898
+ deleted
899
+ };
784
900
  }
785
901
  async _merge(child) {
786
902
  return this.writeLock(async () => {
@@ -788,13 +904,27 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
788
904
  for (const key of child.writeBuffer.keys()) {
789
905
  const lastModLocalVer = this.keyVersions.get(key);
790
906
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
791
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
907
+ return {
908
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
909
+ conflict: {
910
+ key,
911
+ parent: await this.read(key),
912
+ child: await child.read(key)
913
+ }
914
+ };
792
915
  }
793
916
  }
794
917
  for (const key of child.deleteBuffer) {
795
918
  const lastModLocalVer = this.keyVersions.get(key);
796
919
  if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
797
- return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
920
+ return {
921
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
922
+ conflict: {
923
+ key,
924
+ parent: await this.read(key),
925
+ child: await child.read(key)
926
+ }
927
+ };
798
928
  }
799
929
  }
800
930
  const newLocalVersion = this.localVersion + 1;
@@ -821,19 +951,47 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
821
951
  }
822
952
  this.localVersion = newLocalVersion;
823
953
  this.root.activeTransactions.delete(child);
954
+ return null;
824
955
  } else {
825
- this.root.activeTransactions.delete(child);
826
956
  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})`;
957
+ if (child !== this) {
958
+ const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
959
+ for (const key of modifiedKeys) {
960
+ const versions = this.versionIndex.get(key);
961
+ if (versions && versions.length > 0) {
962
+ const lastVer = versions[versions.length - 1].version;
963
+ if (lastVer > child.snapshotVersion) {
964
+ return {
965
+ error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
966
+ conflict: {
967
+ key,
968
+ parent: await this.read(key),
969
+ child: await child.read(key)
970
+ }
971
+ };
972
+ }
834
973
  }
835
974
  }
836
975
  }
976
+ for (const [key, value] of child.writeBuffer) {
977
+ this.writeBuffer.set(key, value);
978
+ this.deleteBuffer.delete(key);
979
+ if (child.createdKeys.has(key)) {
980
+ this.createdKeys.add(key);
981
+ }
982
+ }
983
+ for (const key of child.deleteBuffer) {
984
+ this.deleteBuffer.add(key);
985
+ this.writeBuffer.delete(key);
986
+ this.createdKeys.delete(key);
987
+ const deletedValue = child.deletedValues.get(key);
988
+ if (deletedValue !== void 0) {
989
+ this.deletedValues.set(key, deletedValue);
990
+ }
991
+ if (child.originallyExisted.has(key)) {
992
+ this.originallyExisted.add(key);
993
+ }
994
+ }
837
995
  for (const [key, value] of child.writeBuffer) {
838
996
  await this._diskWrite(key, value, newVersion);
839
997
  }
@@ -841,9 +999,10 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
841
999
  await this._diskDelete(key, newVersion);
842
1000
  }
843
1001
  this.version = newVersion;
1002
+ this.root.activeTransactions.delete(child);
844
1003
  this._cleanupDeletedCache();
1004
+ return null;
845
1005
  }
846
- return null;
847
1006
  });
848
1007
  }
849
1008
  // --- Internal IO Helpers (Root Only) ---
@@ -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.4",
4
4
  "description": "Multiversion Concurrency Control (MVCC) API for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",