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 +17 -0
- package/dist/cjs/index.cjs +219 -60
- package/dist/esm/index.mjs +219 -60
- package/dist/types/core/async/Transaction.d.ts +3 -3
- package/dist/types/core/base/Transaction.d.ts +4 -3
- package/dist/types/core/sync/Transaction.d.ts +3 -3
- package/dist/types/types/index.d.ts +19 -2
- package/package.json +1 -1
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
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
252
|
-
if (
|
|
253
|
-
return {
|
|
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
|
|
259
|
-
if (
|
|
260
|
-
return {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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) ---
|
package/dist/esm/index.mjs
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
223
|
-
if (
|
|
224
|
-
return {
|
|
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
|
|
230
|
-
if (
|
|
231
|
-
return {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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<
|
|
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<
|
|
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:
|
|
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
|
-
|
|
12
|
-
|
|
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>[];
|