dataply 0.0.17-alpha.0 → 0.0.17-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
AsyncMVCCStrategy: () => AsyncMVCCStrategy2,
|
|
34
|
+
AsyncMVCCTransaction: () => AsyncMVCCTransaction2,
|
|
33
35
|
BPTreeAsync: () => BPTreeAsync,
|
|
34
36
|
BPTreeAsyncTransaction: () => BPTreeAsyncTransaction,
|
|
35
37
|
BPTreeSync: () => BPTreeSync,
|
|
@@ -47,6 +49,8 @@ __export(src_exports, {
|
|
|
47
49
|
IndexPageManager: () => IndexPageManager,
|
|
48
50
|
InvertedWeakMap: () => InvertedWeakMap,
|
|
49
51
|
LRUMap: () => LRUMap2,
|
|
52
|
+
MVCCStrategy: () => MVCCStrategy2,
|
|
53
|
+
MVCCTransaction: () => MVCCTransaction2,
|
|
50
54
|
MetadataPageManager: () => MetadataPageManager,
|
|
51
55
|
NumericComparator: () => NumericComparator,
|
|
52
56
|
OverflowPageManager: () => OverflowPageManager,
|
|
@@ -56,6 +60,8 @@ __export(src_exports, {
|
|
|
56
60
|
SerializeStrategyAsync: () => SerializeStrategyAsync,
|
|
57
61
|
SerializeStrategySync: () => SerializeStrategySync,
|
|
58
62
|
StringComparator: () => StringComparator,
|
|
63
|
+
SyncMVCCStrategy: () => SyncMVCCStrategy2,
|
|
64
|
+
SyncMVCCTransaction: () => SyncMVCCTransaction2,
|
|
59
65
|
Transaction: () => Transaction,
|
|
60
66
|
UnknownPageManager: () => UnknownPageManager,
|
|
61
67
|
ValueComparator: () => ValueComparator
|
|
@@ -4561,6 +4567,1125 @@ var InvertedWeakMap = class {
|
|
|
4561
4567
|
}
|
|
4562
4568
|
};
|
|
4563
4569
|
|
|
4570
|
+
// node_modules/mvcc-api/dist/esm/index.mjs
|
|
4571
|
+
var MVCCStrategy2 = class {
|
|
4572
|
+
};
|
|
4573
|
+
var MVCCTransaction2 = class {
|
|
4574
|
+
committed;
|
|
4575
|
+
snapshotVersion;
|
|
4576
|
+
snapshotLocalVersion;
|
|
4577
|
+
writeBuffer;
|
|
4578
|
+
deleteBuffer;
|
|
4579
|
+
createdKeys;
|
|
4580
|
+
// create()로 생성된 키 추적
|
|
4581
|
+
deletedValues;
|
|
4582
|
+
// delete 시 삭제 전 값 저장
|
|
4583
|
+
originallyExisted;
|
|
4584
|
+
// 트랜잭션 시작 시점에 디스크에 존재했던 키 (deleted 결과 필터링용)
|
|
4585
|
+
// Nested Transaction Properties
|
|
4586
|
+
parent;
|
|
4587
|
+
localVersion;
|
|
4588
|
+
// Local version for Nested Conflict Detection
|
|
4589
|
+
keyVersions;
|
|
4590
|
+
// Key -> Local Version (When it was modified locally)
|
|
4591
|
+
// Root Transaction Properties (Only populated if this is Root)
|
|
4592
|
+
root;
|
|
4593
|
+
strategy;
|
|
4594
|
+
version = 0;
|
|
4595
|
+
versionIndex = /* @__PURE__ */ new Map();
|
|
4596
|
+
deletedCache = /* @__PURE__ */ new Map();
|
|
4597
|
+
activeTransactions = /* @__PURE__ */ new Set();
|
|
4598
|
+
constructor(strategy, parent, snapshotVersion) {
|
|
4599
|
+
this.snapshotVersion = snapshotVersion ?? 0;
|
|
4600
|
+
this.writeBuffer = /* @__PURE__ */ new Map();
|
|
4601
|
+
this.deleteBuffer = /* @__PURE__ */ new Set();
|
|
4602
|
+
this.createdKeys = /* @__PURE__ */ new Set();
|
|
4603
|
+
this.deletedValues = /* @__PURE__ */ new Map();
|
|
4604
|
+
this.originallyExisted = /* @__PURE__ */ new Set();
|
|
4605
|
+
this.committed = false;
|
|
4606
|
+
this.parent = parent;
|
|
4607
|
+
this.keyVersions = /* @__PURE__ */ new Map();
|
|
4608
|
+
if (parent) {
|
|
4609
|
+
this.localVersion = parent.localVersion;
|
|
4610
|
+
this.snapshotLocalVersion = parent.localVersion;
|
|
4611
|
+
this.strategy = void 0;
|
|
4612
|
+
this.root = parent.root;
|
|
4613
|
+
} else {
|
|
4614
|
+
if (!strategy) throw new Error("Root Transaction must get Strategy");
|
|
4615
|
+
this.strategy = strategy;
|
|
4616
|
+
this.version = 0;
|
|
4617
|
+
this.localVersion = 0;
|
|
4618
|
+
this.snapshotLocalVersion = 0;
|
|
4619
|
+
this.root = this;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
isRoot() {
|
|
4623
|
+
return !this.parent;
|
|
4624
|
+
}
|
|
4625
|
+
/**
|
|
4626
|
+
* Checks if any ancestor transaction has already been committed.
|
|
4627
|
+
* A nested transaction cannot commit if its parent or any higher ancestor is committed.
|
|
4628
|
+
* @returns True if at least one ancestor is committed, false otherwise.
|
|
4629
|
+
*/
|
|
4630
|
+
hasCommittedAncestor() {
|
|
4631
|
+
let current = this.parent;
|
|
4632
|
+
while (current) {
|
|
4633
|
+
if (current.committed) return true;
|
|
4634
|
+
current = current.parent;
|
|
4635
|
+
}
|
|
4636
|
+
return false;
|
|
4637
|
+
}
|
|
4638
|
+
// --- Internal buffer manipulation helpers ---
|
|
4639
|
+
_bufferCreate(key, value) {
|
|
4640
|
+
this.localVersion++;
|
|
4641
|
+
this.writeBuffer.set(key, value);
|
|
4642
|
+
this.createdKeys.add(key);
|
|
4643
|
+
this.deleteBuffer.delete(key);
|
|
4644
|
+
this.originallyExisted.delete(key);
|
|
4645
|
+
this.keyVersions.set(key, this.localVersion);
|
|
4646
|
+
}
|
|
4647
|
+
_bufferWrite(key, value) {
|
|
4648
|
+
this.localVersion++;
|
|
4649
|
+
this.writeBuffer.set(key, value);
|
|
4650
|
+
this.deleteBuffer.delete(key);
|
|
4651
|
+
this.keyVersions.set(key, this.localVersion);
|
|
4652
|
+
}
|
|
4653
|
+
_bufferDelete(key) {
|
|
4654
|
+
this.localVersion++;
|
|
4655
|
+
this.deleteBuffer.add(key);
|
|
4656
|
+
this.writeBuffer.delete(key);
|
|
4657
|
+
this.createdKeys.delete(key);
|
|
4658
|
+
this.keyVersions.set(key, this.localVersion);
|
|
4659
|
+
}
|
|
4660
|
+
/**
|
|
4661
|
+
* Returns the entries that will be created, updated, and deleted by this transaction.
|
|
4662
|
+
* @returns An object containing arrays of created, updated, and deleted entries.
|
|
4663
|
+
*/
|
|
4664
|
+
getResultEntries() {
|
|
4665
|
+
const created = [];
|
|
4666
|
+
const updated = [];
|
|
4667
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
4668
|
+
if (this.createdKeys.has(key)) {
|
|
4669
|
+
created.push({ key, data });
|
|
4670
|
+
} else {
|
|
4671
|
+
updated.push({ key, data });
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
const deleted = [];
|
|
4675
|
+
for (const key of this.deleteBuffer) {
|
|
4676
|
+
if (!this.originallyExisted.has(key)) continue;
|
|
4677
|
+
const data = this.deletedValues.get(key);
|
|
4678
|
+
if (data !== void 0) {
|
|
4679
|
+
deleted.push({ key, data });
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
return { created, updated, deleted };
|
|
4683
|
+
}
|
|
4684
|
+
/**
|
|
4685
|
+
* Rolls back the transaction.
|
|
4686
|
+
* Clears all buffers and marks the transaction as finished.
|
|
4687
|
+
* @returns The result object with success, created, updated, and deleted keys.
|
|
4688
|
+
*/
|
|
4689
|
+
rollback() {
|
|
4690
|
+
const { created, updated, deleted } = this.getResultEntries();
|
|
4691
|
+
this.writeBuffer.clear();
|
|
4692
|
+
this.deleteBuffer.clear();
|
|
4693
|
+
this.createdKeys.clear();
|
|
4694
|
+
this.deletedValues.clear();
|
|
4695
|
+
this.originallyExisted.clear();
|
|
4696
|
+
this.committed = true;
|
|
4697
|
+
if (this.root !== this) {
|
|
4698
|
+
this.root.activeTransactions.delete(this);
|
|
4699
|
+
}
|
|
4700
|
+
return { success: true, created, updated, deleted };
|
|
4701
|
+
}
|
|
4702
|
+
};
|
|
4703
|
+
var SyncMVCCStrategy2 = class extends MVCCStrategy2 {
|
|
4704
|
+
};
|
|
4705
|
+
var SyncMVCCTransaction2 = class _SyncMVCCTransaction2 extends MVCCTransaction2 {
|
|
4706
|
+
create(key, value) {
|
|
4707
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4708
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && this.read(key) !== null) {
|
|
4709
|
+
throw new Error(`Key already exists: ${key}`);
|
|
4710
|
+
}
|
|
4711
|
+
this._bufferCreate(key, value);
|
|
4712
|
+
return this;
|
|
4713
|
+
}
|
|
4714
|
+
write(key, value) {
|
|
4715
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4716
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || this.read(key) === null)) {
|
|
4717
|
+
throw new Error(`Key not found: ${key}`);
|
|
4718
|
+
}
|
|
4719
|
+
this._bufferWrite(key, value);
|
|
4720
|
+
return this;
|
|
4721
|
+
}
|
|
4722
|
+
delete(key) {
|
|
4723
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4724
|
+
let valueToDelete = null;
|
|
4725
|
+
let wasInWriteBuffer = false;
|
|
4726
|
+
if (this.writeBuffer.has(key)) {
|
|
4727
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
4728
|
+
wasInWriteBuffer = true;
|
|
4729
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
4730
|
+
valueToDelete = this.read(key);
|
|
4731
|
+
}
|
|
4732
|
+
if (valueToDelete === null) {
|
|
4733
|
+
throw new Error(`Key not found: ${key}`);
|
|
4734
|
+
}
|
|
4735
|
+
this.deletedValues.set(key, valueToDelete);
|
|
4736
|
+
if (!wasInWriteBuffer || !this.createdKeys.has(key)) {
|
|
4737
|
+
this.originallyExisted.add(key);
|
|
4738
|
+
}
|
|
4739
|
+
this._bufferDelete(key);
|
|
4740
|
+
return this;
|
|
4741
|
+
}
|
|
4742
|
+
createNested() {
|
|
4743
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4744
|
+
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
4745
|
+
const child = new _SyncMVCCTransaction2(void 0, this, childVersion);
|
|
4746
|
+
this.root.activeTransactions.add(child);
|
|
4747
|
+
return child;
|
|
4748
|
+
}
|
|
4749
|
+
read(key) {
|
|
4750
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4751
|
+
if (this.writeBuffer.has(key)) return this.writeBuffer.get(key);
|
|
4752
|
+
if (this.deleteBuffer.has(key)) return null;
|
|
4753
|
+
return this.root._diskRead(key, this.snapshotVersion);
|
|
4754
|
+
}
|
|
4755
|
+
exists(key) {
|
|
4756
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
4757
|
+
if (this.deleteBuffer.has(key)) return false;
|
|
4758
|
+
if (this.writeBuffer.has(key)) return true;
|
|
4759
|
+
return this.root._diskExists(key, this.snapshotVersion);
|
|
4760
|
+
}
|
|
4761
|
+
_readSnapshot(key, snapshotVersion, snapshotLocalVersion) {
|
|
4762
|
+
if (this.writeBuffer.has(key)) {
|
|
4763
|
+
const keyModVersion = this.keyVersions.get(key);
|
|
4764
|
+
if (snapshotLocalVersion === void 0 || keyModVersion === void 0 || keyModVersion <= snapshotLocalVersion) {
|
|
4765
|
+
return this.writeBuffer.get(key);
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
if (this.deleteBuffer.has(key)) {
|
|
4769
|
+
const keyModVersion = this.keyVersions.get(key);
|
|
4770
|
+
if (snapshotLocalVersion === void 0 || keyModVersion === void 0 || keyModVersion <= snapshotLocalVersion) {
|
|
4771
|
+
return null;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
if (this.parent) {
|
|
4775
|
+
return this.parent._readSnapshot(key, snapshotVersion, this.snapshotLocalVersion);
|
|
4776
|
+
} else {
|
|
4777
|
+
return this._diskRead(key, snapshotVersion);
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
commit(label) {
|
|
4781
|
+
const { created, updated, deleted } = this.getResultEntries();
|
|
4782
|
+
if (this.committed) {
|
|
4783
|
+
return {
|
|
4784
|
+
label,
|
|
4785
|
+
success: false,
|
|
4786
|
+
error: "Transaction already committed",
|
|
4787
|
+
conflict: void 0,
|
|
4788
|
+
created,
|
|
4789
|
+
updated,
|
|
4790
|
+
deleted
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
if (this.hasCommittedAncestor()) {
|
|
4794
|
+
return {
|
|
4795
|
+
label,
|
|
4796
|
+
success: false,
|
|
4797
|
+
error: "Ancestor transaction already committed",
|
|
4798
|
+
conflict: void 0,
|
|
4799
|
+
created,
|
|
4800
|
+
updated,
|
|
4801
|
+
deleted
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
if (this.parent) {
|
|
4805
|
+
const failure = this.parent._merge(this);
|
|
4806
|
+
if (failure) {
|
|
4807
|
+
return {
|
|
4808
|
+
label,
|
|
4809
|
+
success: false,
|
|
4810
|
+
error: failure.error,
|
|
4811
|
+
conflict: failure.conflict,
|
|
4812
|
+
created,
|
|
4813
|
+
updated,
|
|
4814
|
+
deleted
|
|
4815
|
+
};
|
|
4816
|
+
}
|
|
4817
|
+
this.committed = true;
|
|
4818
|
+
} else {
|
|
4819
|
+
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
4820
|
+
const failure = this._merge(this);
|
|
4821
|
+
if (failure) {
|
|
4822
|
+
return {
|
|
4823
|
+
label,
|
|
4824
|
+
success: false,
|
|
4825
|
+
error: failure.error,
|
|
4826
|
+
conflict: failure.conflict,
|
|
4827
|
+
created: [],
|
|
4828
|
+
updated: [],
|
|
4829
|
+
deleted: []
|
|
4830
|
+
};
|
|
4831
|
+
}
|
|
4832
|
+
this.writeBuffer.clear();
|
|
4833
|
+
this.deleteBuffer.clear();
|
|
4834
|
+
this.createdKeys.clear();
|
|
4835
|
+
this.deletedValues.clear();
|
|
4836
|
+
this.originallyExisted.clear();
|
|
4837
|
+
this.keyVersions.clear();
|
|
4838
|
+
this.localVersion = 0;
|
|
4839
|
+
this.snapshotVersion = this.version;
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
return {
|
|
4843
|
+
label,
|
|
4844
|
+
success: true,
|
|
4845
|
+
created,
|
|
4846
|
+
updated,
|
|
4847
|
+
deleted
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
_merge(child) {
|
|
4851
|
+
if (this.parent) {
|
|
4852
|
+
for (const key of child.writeBuffer.keys()) {
|
|
4853
|
+
const lastModLocalVer = this.keyVersions.get(key);
|
|
4854
|
+
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
4855
|
+
return {
|
|
4856
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
|
|
4857
|
+
conflict: {
|
|
4858
|
+
key,
|
|
4859
|
+
parent: this.read(key),
|
|
4860
|
+
child: child.read(key)
|
|
4861
|
+
}
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
for (const key of child.deleteBuffer) {
|
|
4866
|
+
const lastModLocalVer = this.keyVersions.get(key);
|
|
4867
|
+
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
4868
|
+
return {
|
|
4869
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
|
|
4870
|
+
conflict: {
|
|
4871
|
+
key,
|
|
4872
|
+
parent: this.read(key),
|
|
4873
|
+
child: child.read(key)
|
|
4874
|
+
}
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
const newLocalVersion = this.localVersion + 1;
|
|
4879
|
+
for (const key of child.writeBuffer.keys()) {
|
|
4880
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
4881
|
+
this.deleteBuffer.delete(key);
|
|
4882
|
+
this.keyVersions.set(key, newLocalVersion);
|
|
4883
|
+
if (child.createdKeys.has(key)) {
|
|
4884
|
+
this.createdKeys.add(key);
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
for (const key of child.deleteBuffer) {
|
|
4888
|
+
this.deleteBuffer.add(key);
|
|
4889
|
+
this.writeBuffer.delete(key);
|
|
4890
|
+
this.createdKeys.delete(key);
|
|
4891
|
+
this.keyVersions.set(key, newLocalVersion);
|
|
4892
|
+
const deletedValue = child.deletedValues.get(key);
|
|
4893
|
+
if (deletedValue !== void 0) {
|
|
4894
|
+
this.deletedValues.set(key, deletedValue);
|
|
4895
|
+
}
|
|
4896
|
+
if (child.originallyExisted.has(key)) {
|
|
4897
|
+
this.originallyExisted.add(key);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
this.localVersion = newLocalVersion;
|
|
4901
|
+
this.root.activeTransactions.delete(child);
|
|
4902
|
+
} else {
|
|
4903
|
+
const newVersion = this.version + 1;
|
|
4904
|
+
if (child !== this) {
|
|
4905
|
+
const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
|
|
4906
|
+
for (const key of modifiedKeys) {
|
|
4907
|
+
const versions = this.versionIndex.get(key);
|
|
4908
|
+
if (versions && versions.length > 0) {
|
|
4909
|
+
const lastVer = versions[versions.length - 1].version;
|
|
4910
|
+
if (lastVer > child.snapshotVersion) {
|
|
4911
|
+
return {
|
|
4912
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
|
|
4913
|
+
conflict: {
|
|
4914
|
+
key,
|
|
4915
|
+
parent: this.read(key),
|
|
4916
|
+
child: child.read(key)
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
for (const [key, value] of child.writeBuffer) {
|
|
4924
|
+
this.writeBuffer.set(key, value);
|
|
4925
|
+
this.deleteBuffer.delete(key);
|
|
4926
|
+
if (child.createdKeys.has(key)) {
|
|
4927
|
+
this.createdKeys.add(key);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
for (const key of child.deleteBuffer) {
|
|
4931
|
+
this.deleteBuffer.add(key);
|
|
4932
|
+
this.writeBuffer.delete(key);
|
|
4933
|
+
this.createdKeys.delete(key);
|
|
4934
|
+
const deletedValue = child.deletedValues.get(key);
|
|
4935
|
+
if (deletedValue !== void 0) {
|
|
4936
|
+
this.deletedValues.set(key, deletedValue);
|
|
4937
|
+
}
|
|
4938
|
+
if (child.originallyExisted.has(key)) {
|
|
4939
|
+
this.originallyExisted.add(key);
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
for (const [key, value] of child.writeBuffer) {
|
|
4943
|
+
this._diskWrite(key, value, newVersion);
|
|
4944
|
+
}
|
|
4945
|
+
for (const key of child.deleteBuffer) {
|
|
4946
|
+
this._diskDelete(key, newVersion);
|
|
4947
|
+
}
|
|
4948
|
+
this.version = newVersion;
|
|
4949
|
+
this.root.activeTransactions.delete(child);
|
|
4950
|
+
this._cleanupDeletedCache();
|
|
4951
|
+
}
|
|
4952
|
+
return null;
|
|
4953
|
+
}
|
|
4954
|
+
// --- Internal IO Helpers (Root Only) ---
|
|
4955
|
+
_diskWrite(key, value, version) {
|
|
4956
|
+
const strategy = this.strategy;
|
|
4957
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
4958
|
+
if (strategy.exists(key)) {
|
|
4959
|
+
const currentVal = strategy.read(key);
|
|
4960
|
+
if (!this.deletedCache.has(key)) this.deletedCache.set(key, []);
|
|
4961
|
+
this.deletedCache.get(key).push({
|
|
4962
|
+
value: currentVal,
|
|
4963
|
+
deletedAtVersion: version
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
strategy.write(key, value);
|
|
4967
|
+
if (!this.versionIndex.has(key)) this.versionIndex.set(key, []);
|
|
4968
|
+
this.versionIndex.get(key).push({ version, exists: true });
|
|
4969
|
+
}
|
|
4970
|
+
_diskRead(key, snapshotVersion) {
|
|
4971
|
+
const strategy = this.strategy;
|
|
4972
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
4973
|
+
const versions = this.versionIndex.get(key);
|
|
4974
|
+
if (!versions) {
|
|
4975
|
+
return strategy.exists(key) ? strategy.read(key) : null;
|
|
4976
|
+
}
|
|
4977
|
+
let targetVerObj = null;
|
|
4978
|
+
let nextVerObj = null;
|
|
4979
|
+
for (const v of versions) {
|
|
4980
|
+
if (v.version <= snapshotVersion) {
|
|
4981
|
+
targetVerObj = v;
|
|
4982
|
+
} else {
|
|
4983
|
+
nextVerObj = v;
|
|
4984
|
+
break;
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
if (!targetVerObj) {
|
|
4988
|
+
if (nextVerObj) {
|
|
4989
|
+
const cached2 = this.deletedCache.get(key);
|
|
4990
|
+
if (cached2) {
|
|
4991
|
+
const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
|
|
4992
|
+
if (match) return match.value;
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
return null;
|
|
4996
|
+
}
|
|
4997
|
+
if (!targetVerObj.exists) return null;
|
|
4998
|
+
if (!nextVerObj) {
|
|
4999
|
+
return strategy.read(key);
|
|
5000
|
+
}
|
|
5001
|
+
const cached = this.deletedCache.get(key);
|
|
5002
|
+
if (cached) {
|
|
5003
|
+
const match = cached.find((c) => c.deletedAtVersion === nextVerObj.version);
|
|
5004
|
+
if (match) return match.value;
|
|
5005
|
+
}
|
|
5006
|
+
return null;
|
|
5007
|
+
}
|
|
5008
|
+
_diskExists(key, snapshotVersion) {
|
|
5009
|
+
const strategy = this.strategy;
|
|
5010
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5011
|
+
const versions = this.versionIndex.get(key);
|
|
5012
|
+
if (!versions) {
|
|
5013
|
+
return strategy.exists(key);
|
|
5014
|
+
}
|
|
5015
|
+
let targetVerObj = null;
|
|
5016
|
+
for (const v of versions) {
|
|
5017
|
+
if (v.version <= snapshotVersion) {
|
|
5018
|
+
targetVerObj = v;
|
|
5019
|
+
} else {
|
|
5020
|
+
break;
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
if (!targetVerObj) return strategy.exists(key);
|
|
5024
|
+
return targetVerObj.exists;
|
|
5025
|
+
}
|
|
5026
|
+
_diskDelete(key, snapshotVersion) {
|
|
5027
|
+
const strategy = this.strategy;
|
|
5028
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5029
|
+
if (strategy.exists(key)) {
|
|
5030
|
+
const currentVal = strategy.read(key);
|
|
5031
|
+
if (!this.deletedCache.has(key)) this.deletedCache.set(key, []);
|
|
5032
|
+
this.deletedCache.get(key).push({
|
|
5033
|
+
value: currentVal,
|
|
5034
|
+
deletedAtVersion: snapshotVersion
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
5037
|
+
strategy.delete(key);
|
|
5038
|
+
if (!this.versionIndex.has(key)) this.versionIndex.set(key, []);
|
|
5039
|
+
this.versionIndex.get(key).push({ version: snapshotVersion, exists: false });
|
|
5040
|
+
}
|
|
5041
|
+
_cleanupDeletedCache() {
|
|
5042
|
+
if (this.deletedCache.size === 0) return;
|
|
5043
|
+
let minActiveVersion = this.version;
|
|
5044
|
+
if (this.activeTransactions.size > 0) {
|
|
5045
|
+
for (const tx of this.activeTransactions) {
|
|
5046
|
+
if (!tx.committed && tx.snapshotVersion < minActiveVersion) {
|
|
5047
|
+
minActiveVersion = tx.snapshotVersion;
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
for (const [key, cachedList] of this.deletedCache) {
|
|
5052
|
+
const remaining = cachedList.filter((item) => item.deletedAtVersion > minActiveVersion);
|
|
5053
|
+
if (remaining.length === 0) {
|
|
5054
|
+
this.deletedCache.delete(key);
|
|
5055
|
+
} else {
|
|
5056
|
+
this.deletedCache.set(key, remaining);
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
};
|
|
5061
|
+
var AsyncMVCCStrategy2 = class extends MVCCStrategy2 {
|
|
5062
|
+
};
|
|
5063
|
+
var Ryoiki4 = class _Ryoiki4 {
|
|
5064
|
+
readings;
|
|
5065
|
+
writings;
|
|
5066
|
+
readQueue;
|
|
5067
|
+
writeQueue;
|
|
5068
|
+
static async CatchError(promise) {
|
|
5069
|
+
return await promise.then((v) => [void 0, v]).catch((err) => [err]);
|
|
5070
|
+
}
|
|
5071
|
+
static IsRangeOverlap(a, b) {
|
|
5072
|
+
const [start1, end1] = a;
|
|
5073
|
+
const [start2, end2] = b;
|
|
5074
|
+
if (end1 <= start2 || end2 <= start1) {
|
|
5075
|
+
return false;
|
|
5076
|
+
}
|
|
5077
|
+
return true;
|
|
5078
|
+
}
|
|
5079
|
+
static ERR_ALREADY_EXISTS(lockId) {
|
|
5080
|
+
return new Error(`The '${lockId}' task already existing in queue or running.`);
|
|
5081
|
+
}
|
|
5082
|
+
static ERR_NOT_EXISTS(lockId) {
|
|
5083
|
+
return new Error(`The '${lockId}' task not existing in task queue.`);
|
|
5084
|
+
}
|
|
5085
|
+
static ERR_TIMEOUT(lockId, timeout) {
|
|
5086
|
+
return new Error(`The task with ID '${lockId}' failed to acquire the lock within the timeout(${timeout}ms).`);
|
|
5087
|
+
}
|
|
5088
|
+
/**
|
|
5089
|
+
* Constructs a new instance of the Ryoiki class.
|
|
5090
|
+
*/
|
|
5091
|
+
constructor() {
|
|
5092
|
+
this.readings = /* @__PURE__ */ new Map();
|
|
5093
|
+
this.writings = /* @__PURE__ */ new Map();
|
|
5094
|
+
this.readQueue = /* @__PURE__ */ new Map();
|
|
5095
|
+
this.writeQueue = /* @__PURE__ */ new Map();
|
|
5096
|
+
}
|
|
5097
|
+
/**
|
|
5098
|
+
* Creates a range based on a start value and length.
|
|
5099
|
+
* @param start - The starting value of the range.
|
|
5100
|
+
* @param length - The length of the range.
|
|
5101
|
+
* @returns A range tuple [start, start + length].
|
|
5102
|
+
*/
|
|
5103
|
+
range(start, length) {
|
|
5104
|
+
return [start, start + length];
|
|
5105
|
+
}
|
|
5106
|
+
rangeOverlapping(tasks, range) {
|
|
5107
|
+
return Array.from(tasks.values()).some((t) => _Ryoiki4.IsRangeOverlap(t.range, range));
|
|
5108
|
+
}
|
|
5109
|
+
isSameRange(a, b) {
|
|
5110
|
+
const [a1, a2] = a;
|
|
5111
|
+
const [b1, b2] = b;
|
|
5112
|
+
return a1 === b1 && a2 === b2;
|
|
5113
|
+
}
|
|
5114
|
+
fetchUnitAndRun(queue, workspaces) {
|
|
5115
|
+
for (const [id, unit] of queue) {
|
|
5116
|
+
if (!unit.condition()) {
|
|
5117
|
+
continue;
|
|
5118
|
+
}
|
|
5119
|
+
this._alloc(queue, workspaces, id);
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
_handleOverload(args, handlers, argPatterns) {
|
|
5123
|
+
for (const [key, pattern] of Object.entries(argPatterns)) {
|
|
5124
|
+
if (this._matchArgs(args, pattern)) {
|
|
5125
|
+
return handlers[key](...args);
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
throw new Error("Invalid arguments");
|
|
5129
|
+
}
|
|
5130
|
+
_matchArgs(args, pattern) {
|
|
5131
|
+
return args.every((arg, index) => {
|
|
5132
|
+
const expectedType = pattern[index];
|
|
5133
|
+
if (expectedType === void 0) return typeof arg === "undefined";
|
|
5134
|
+
if (expectedType === Function) return typeof arg === "function";
|
|
5135
|
+
if (expectedType === Number) return typeof arg === "number";
|
|
5136
|
+
if (expectedType === Array) return Array.isArray(arg);
|
|
5137
|
+
return false;
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
_createRandomId() {
|
|
5141
|
+
const timestamp = Date.now().toString(36);
|
|
5142
|
+
const random = Math.random().toString(36).substring(2);
|
|
5143
|
+
return `${timestamp}${random}`;
|
|
5144
|
+
}
|
|
5145
|
+
_alloc(queue, workspaces, lockId) {
|
|
5146
|
+
const unit = queue.get(lockId);
|
|
5147
|
+
if (!unit) {
|
|
5148
|
+
throw _Ryoiki4.ERR_NOT_EXISTS(lockId);
|
|
5149
|
+
}
|
|
5150
|
+
workspaces.set(lockId, unit);
|
|
5151
|
+
queue.delete(lockId);
|
|
5152
|
+
unit.alloc();
|
|
5153
|
+
}
|
|
5154
|
+
_free(workspaces, lockId) {
|
|
5155
|
+
const unit = workspaces.get(lockId);
|
|
5156
|
+
if (!unit) {
|
|
5157
|
+
throw _Ryoiki4.ERR_NOT_EXISTS(lockId);
|
|
5158
|
+
}
|
|
5159
|
+
workspaces.delete(lockId);
|
|
5160
|
+
unit.free();
|
|
5161
|
+
}
|
|
5162
|
+
_lock(queue, range, timeout, task, condition) {
|
|
5163
|
+
return new Promise((resolve, reject) => {
|
|
5164
|
+
let timeoutId = null;
|
|
5165
|
+
if (timeout >= 0) {
|
|
5166
|
+
timeoutId = setTimeout(() => {
|
|
5167
|
+
reject(_Ryoiki4.ERR_TIMEOUT(id, timeout));
|
|
5168
|
+
}, timeout);
|
|
5169
|
+
}
|
|
5170
|
+
const id = this._createRandomId();
|
|
5171
|
+
const alloc = async () => {
|
|
5172
|
+
if (timeoutId !== null) {
|
|
5173
|
+
clearTimeout(timeoutId);
|
|
5174
|
+
}
|
|
5175
|
+
const [err, v] = await _Ryoiki4.CatchError(task(id));
|
|
5176
|
+
if (err) reject(err);
|
|
5177
|
+
else resolve(v);
|
|
5178
|
+
};
|
|
5179
|
+
const fetch = () => {
|
|
5180
|
+
this.fetchUnitAndRun(this.readQueue, this.readings);
|
|
5181
|
+
this.fetchUnitAndRun(this.writeQueue, this.writings);
|
|
5182
|
+
};
|
|
5183
|
+
queue.set(id, { id, range, condition, alloc, free: fetch });
|
|
5184
|
+
fetch();
|
|
5185
|
+
});
|
|
5186
|
+
}
|
|
5187
|
+
_checkWorking(range, workspaces) {
|
|
5188
|
+
let isLocked = false;
|
|
5189
|
+
for (const lock of workspaces.values()) {
|
|
5190
|
+
if (_Ryoiki4.IsRangeOverlap(range, lock.range)) {
|
|
5191
|
+
isLocked = true;
|
|
5192
|
+
break;
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
return isLocked;
|
|
5196
|
+
}
|
|
5197
|
+
/**
|
|
5198
|
+
* Checks if there is any active read lock within the specified range.
|
|
5199
|
+
* @param range The range to check for active read locks.
|
|
5200
|
+
* @returns `true` if there is an active read lock within the range, `false` otherwise.
|
|
5201
|
+
*/
|
|
5202
|
+
isReading(range) {
|
|
5203
|
+
return this._checkWorking(range, this.readings);
|
|
5204
|
+
}
|
|
5205
|
+
/**
|
|
5206
|
+
* Checks if there is any active write lock within the specified range.
|
|
5207
|
+
* @param range The range to check for active write locks.
|
|
5208
|
+
* @returns `true` if there is an active write lock within the range, `false` otherwise.
|
|
5209
|
+
*/
|
|
5210
|
+
isWriting(range) {
|
|
5211
|
+
return this._checkWorking(range, this.writings);
|
|
5212
|
+
}
|
|
5213
|
+
/**
|
|
5214
|
+
* Checks if a read lock can be acquired within the specified range.
|
|
5215
|
+
* @param range The range to check for read lock availability.
|
|
5216
|
+
* @returns `true` if a read lock can be acquired, `false` otherwise.
|
|
5217
|
+
*/
|
|
5218
|
+
canRead(range) {
|
|
5219
|
+
const writing = this.isWriting(range);
|
|
5220
|
+
return !writing;
|
|
5221
|
+
}
|
|
5222
|
+
/**
|
|
5223
|
+
* Checks if a write lock can be acquired within the specified range.
|
|
5224
|
+
* @param range The range to check for write lock availability.
|
|
5225
|
+
* @returns `true` if a write lock can be acquired, `false` otherwise.
|
|
5226
|
+
*/
|
|
5227
|
+
canWrite(range) {
|
|
5228
|
+
const reading = this.isReading(range);
|
|
5229
|
+
const writing = this.isWriting(range);
|
|
5230
|
+
return !reading && !writing;
|
|
5231
|
+
}
|
|
5232
|
+
/**
|
|
5233
|
+
* Internal implementation of the read lock. Handles both overloads.
|
|
5234
|
+
* @template T - The return type of the task.
|
|
5235
|
+
* @param arg0 - Either a range or a task callback.
|
|
5236
|
+
* If a range is provided, the task is the second argument.
|
|
5237
|
+
* @param arg1 - The task to execute, required if a range is provided.
|
|
5238
|
+
* @param arg2 - The timeout for acquiring the lock.
|
|
5239
|
+
* If the lock cannot be acquired within this period, an error will be thrown.
|
|
5240
|
+
* If this value is not provided, no timeout will be set.
|
|
5241
|
+
* @returns A promise resolving to the result of the task execution.
|
|
5242
|
+
*/
|
|
5243
|
+
readLock(arg0, arg1, arg2) {
|
|
5244
|
+
const [range, task, timeout] = this._handleOverload(
|
|
5245
|
+
[arg0, arg1, arg2],
|
|
5246
|
+
{
|
|
5247
|
+
rangeTask: (range2, task2) => [range2, task2, -1],
|
|
5248
|
+
rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
|
|
5249
|
+
task: (task2) => [[-Infinity, Infinity], task2, -1],
|
|
5250
|
+
taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
|
|
5251
|
+
},
|
|
5252
|
+
{
|
|
5253
|
+
task: [Function],
|
|
5254
|
+
taskTimeout: [Function, Number],
|
|
5255
|
+
rangeTask: [Array, Function],
|
|
5256
|
+
rangeTaskTimeout: [Array, Function, Number]
|
|
5257
|
+
}
|
|
5258
|
+
);
|
|
5259
|
+
return this._lock(
|
|
5260
|
+
this.readQueue,
|
|
5261
|
+
range,
|
|
5262
|
+
timeout,
|
|
5263
|
+
task,
|
|
5264
|
+
() => !this.rangeOverlapping(this.writings, range)
|
|
5265
|
+
);
|
|
5266
|
+
}
|
|
5267
|
+
/**
|
|
5268
|
+
* Internal implementation of the write lock. Handles both overloads.
|
|
5269
|
+
* @template T - The return type of the task.
|
|
5270
|
+
* @param arg0 - Either a range or a task callback.
|
|
5271
|
+
* If a range is provided, the task is the second argument.
|
|
5272
|
+
* @param arg1 - The task to execute, required if a range is provided.
|
|
5273
|
+
* @param arg2 - The timeout for acquiring the lock.
|
|
5274
|
+
* If the lock cannot be acquired within this period, an error will be thrown.
|
|
5275
|
+
* If this value is not provided, no timeout will be set.
|
|
5276
|
+
* @returns A promise resolving to the result of the task execution.
|
|
5277
|
+
*/
|
|
5278
|
+
writeLock(arg0, arg1, arg2) {
|
|
5279
|
+
const [range, task, timeout] = this._handleOverload(
|
|
5280
|
+
[arg0, arg1, arg2],
|
|
5281
|
+
{
|
|
5282
|
+
rangeTask: (range2, task2) => [range2, task2, -1],
|
|
5283
|
+
rangeTaskTimeout: (range2, task2, timeout2) => [range2, task2, timeout2],
|
|
5284
|
+
task: (task2) => [[-Infinity, Infinity], task2, -1],
|
|
5285
|
+
taskTimeout: (task2, timeout2) => [[-Infinity, Infinity], task2, timeout2]
|
|
5286
|
+
},
|
|
5287
|
+
{
|
|
5288
|
+
task: [Function],
|
|
5289
|
+
taskTimeout: [Function, Number],
|
|
5290
|
+
rangeTask: [Array, Function],
|
|
5291
|
+
rangeTaskTimeout: [Array, Function, Number]
|
|
5292
|
+
}
|
|
5293
|
+
);
|
|
5294
|
+
return this._lock(
|
|
5295
|
+
this.writeQueue,
|
|
5296
|
+
range,
|
|
5297
|
+
timeout,
|
|
5298
|
+
task,
|
|
5299
|
+
() => {
|
|
5300
|
+
return !this.rangeOverlapping(this.writings, range) && !this.rangeOverlapping(this.readings, range);
|
|
5301
|
+
}
|
|
5302
|
+
);
|
|
5303
|
+
}
|
|
5304
|
+
/**
|
|
5305
|
+
* Releases a read lock by its lock ID.
|
|
5306
|
+
* @param lockId - The unique identifier for the lock to release.
|
|
5307
|
+
*/
|
|
5308
|
+
readUnlock(lockId) {
|
|
5309
|
+
this._free(this.readings, lockId);
|
|
5310
|
+
}
|
|
5311
|
+
/**
|
|
5312
|
+
* Releases a write lock by its lock ID.
|
|
5313
|
+
* @param lockId - The unique identifier for the lock to release.
|
|
5314
|
+
*/
|
|
5315
|
+
writeUnlock(lockId) {
|
|
5316
|
+
this._free(this.writings, lockId);
|
|
5317
|
+
}
|
|
5318
|
+
};
|
|
5319
|
+
var AsyncMVCCTransaction2 = class _AsyncMVCCTransaction2 extends MVCCTransaction2 {
|
|
5320
|
+
lock = new Ryoiki4();
|
|
5321
|
+
async writeLock(fn) {
|
|
5322
|
+
let lockId;
|
|
5323
|
+
return this.lock.writeLock(async (_lockId) => {
|
|
5324
|
+
lockId = _lockId;
|
|
5325
|
+
return fn();
|
|
5326
|
+
}).finally(() => {
|
|
5327
|
+
this.lock.writeUnlock(lockId);
|
|
5328
|
+
});
|
|
5329
|
+
}
|
|
5330
|
+
async create(key, value) {
|
|
5331
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5332
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && await this.read(key) !== null) {
|
|
5333
|
+
throw new Error(`Key already exists: ${key}`);
|
|
5334
|
+
}
|
|
5335
|
+
this._bufferCreate(key, value);
|
|
5336
|
+
return this;
|
|
5337
|
+
}
|
|
5338
|
+
async write(key, value) {
|
|
5339
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5340
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || await this.read(key) === null)) {
|
|
5341
|
+
throw new Error(`Key not found: ${key}`);
|
|
5342
|
+
}
|
|
5343
|
+
this._bufferWrite(key, value);
|
|
5344
|
+
return this;
|
|
5345
|
+
}
|
|
5346
|
+
async delete(key) {
|
|
5347
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5348
|
+
let valueToDelete = null;
|
|
5349
|
+
let wasInWriteBuffer = false;
|
|
5350
|
+
if (this.writeBuffer.has(key)) {
|
|
5351
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
5352
|
+
wasInWriteBuffer = true;
|
|
5353
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
5354
|
+
valueToDelete = await this.read(key);
|
|
5355
|
+
}
|
|
5356
|
+
if (valueToDelete === null) {
|
|
5357
|
+
throw new Error(`Key not found: ${key}`);
|
|
5358
|
+
}
|
|
5359
|
+
this.deletedValues.set(key, valueToDelete);
|
|
5360
|
+
if (!wasInWriteBuffer || !this.createdKeys.has(key)) {
|
|
5361
|
+
this.originallyExisted.add(key);
|
|
5362
|
+
}
|
|
5363
|
+
this._bufferDelete(key);
|
|
5364
|
+
return this;
|
|
5365
|
+
}
|
|
5366
|
+
createNested() {
|
|
5367
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5368
|
+
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
5369
|
+
const child = new _AsyncMVCCTransaction2(void 0, this, childVersion);
|
|
5370
|
+
this.root.activeTransactions.add(child);
|
|
5371
|
+
return child;
|
|
5372
|
+
}
|
|
5373
|
+
async read(key) {
|
|
5374
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5375
|
+
if (this.writeBuffer.has(key)) return this.writeBuffer.get(key);
|
|
5376
|
+
if (this.deleteBuffer.has(key)) return null;
|
|
5377
|
+
return this.root._diskRead(key, this.snapshotVersion);
|
|
5378
|
+
}
|
|
5379
|
+
async exists(key) {
|
|
5380
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
5381
|
+
if (this.deleteBuffer.has(key)) return false;
|
|
5382
|
+
if (this.writeBuffer.has(key)) return true;
|
|
5383
|
+
return this.root._diskExists(key, this.snapshotVersion);
|
|
5384
|
+
}
|
|
5385
|
+
async _readSnapshot(key, snapshotVersion, snapshotLocalVersion) {
|
|
5386
|
+
if (this.writeBuffer.has(key)) {
|
|
5387
|
+
const keyModVersion = this.keyVersions.get(key);
|
|
5388
|
+
if (snapshotLocalVersion === void 0 || keyModVersion === void 0 || keyModVersion <= snapshotLocalVersion) {
|
|
5389
|
+
return this.writeBuffer.get(key);
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
if (this.deleteBuffer.has(key)) {
|
|
5393
|
+
const keyModVersion = this.keyVersions.get(key);
|
|
5394
|
+
if (snapshotLocalVersion === void 0 || keyModVersion === void 0 || keyModVersion <= snapshotLocalVersion) {
|
|
5395
|
+
return null;
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
if (this.parent) {
|
|
5399
|
+
return this.parent._readSnapshot(key, snapshotVersion, this.snapshotLocalVersion);
|
|
5400
|
+
} else {
|
|
5401
|
+
return this._diskRead(key, snapshotVersion);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
async commit(label) {
|
|
5405
|
+
const { created, updated, deleted } = this.getResultEntries();
|
|
5406
|
+
if (this.committed) {
|
|
5407
|
+
return {
|
|
5408
|
+
label,
|
|
5409
|
+
success: false,
|
|
5410
|
+
error: "Transaction already committed",
|
|
5411
|
+
conflict: void 0,
|
|
5412
|
+
created,
|
|
5413
|
+
updated,
|
|
5414
|
+
deleted
|
|
5415
|
+
};
|
|
5416
|
+
}
|
|
5417
|
+
if (this.hasCommittedAncestor()) {
|
|
5418
|
+
return {
|
|
5419
|
+
label,
|
|
5420
|
+
success: false,
|
|
5421
|
+
error: "Ancestor transaction already committed",
|
|
5422
|
+
conflict: void 0,
|
|
5423
|
+
created,
|
|
5424
|
+
updated,
|
|
5425
|
+
deleted
|
|
5426
|
+
};
|
|
5427
|
+
}
|
|
5428
|
+
if (this.parent) {
|
|
5429
|
+
const failure = await this.parent._merge(this);
|
|
5430
|
+
if (failure) {
|
|
5431
|
+
return {
|
|
5432
|
+
label,
|
|
5433
|
+
success: false,
|
|
5434
|
+
error: failure.error,
|
|
5435
|
+
conflict: failure.conflict,
|
|
5436
|
+
created,
|
|
5437
|
+
updated,
|
|
5438
|
+
deleted
|
|
5439
|
+
};
|
|
5440
|
+
}
|
|
5441
|
+
this.committed = true;
|
|
5442
|
+
} else {
|
|
5443
|
+
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
5444
|
+
const failure = await this._merge(this);
|
|
5445
|
+
if (failure) {
|
|
5446
|
+
return {
|
|
5447
|
+
label,
|
|
5448
|
+
success: false,
|
|
5449
|
+
error: failure.error,
|
|
5450
|
+
conflict: failure.conflict,
|
|
5451
|
+
created: [],
|
|
5452
|
+
updated: [],
|
|
5453
|
+
deleted: []
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
this.writeBuffer.clear();
|
|
5457
|
+
this.deleteBuffer.clear();
|
|
5458
|
+
this.createdKeys.clear();
|
|
5459
|
+
this.deletedValues.clear();
|
|
5460
|
+
this.originallyExisted.clear();
|
|
5461
|
+
this.keyVersions.clear();
|
|
5462
|
+
this.localVersion = 0;
|
|
5463
|
+
this.snapshotVersion = this.version;
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
return {
|
|
5467
|
+
label,
|
|
5468
|
+
success: true,
|
|
5469
|
+
created,
|
|
5470
|
+
updated,
|
|
5471
|
+
deleted
|
|
5472
|
+
};
|
|
5473
|
+
}
|
|
5474
|
+
async _merge(child) {
|
|
5475
|
+
return this.writeLock(async () => {
|
|
5476
|
+
if (this.parent) {
|
|
5477
|
+
for (const key of child.writeBuffer.keys()) {
|
|
5478
|
+
const lastModLocalVer = this.keyVersions.get(key);
|
|
5479
|
+
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
5480
|
+
return {
|
|
5481
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
|
|
5482
|
+
conflict: {
|
|
5483
|
+
key,
|
|
5484
|
+
parent: await this.read(key),
|
|
5485
|
+
child: await child.read(key)
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
for (const key of child.deleteBuffer) {
|
|
5491
|
+
const lastModLocalVer = this.keyVersions.get(key);
|
|
5492
|
+
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
5493
|
+
return {
|
|
5494
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`,
|
|
5495
|
+
conflict: {
|
|
5496
|
+
key,
|
|
5497
|
+
parent: await this.read(key),
|
|
5498
|
+
child: await child.read(key)
|
|
5499
|
+
}
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
const newLocalVersion = this.localVersion + 1;
|
|
5504
|
+
for (const key of child.writeBuffer.keys()) {
|
|
5505
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
5506
|
+
this.deleteBuffer.delete(key);
|
|
5507
|
+
this.keyVersions.set(key, newLocalVersion);
|
|
5508
|
+
if (child.createdKeys.has(key)) {
|
|
5509
|
+
this.createdKeys.add(key);
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
for (const key of child.deleteBuffer) {
|
|
5513
|
+
this.deleteBuffer.add(key);
|
|
5514
|
+
this.writeBuffer.delete(key);
|
|
5515
|
+
this.createdKeys.delete(key);
|
|
5516
|
+
this.keyVersions.set(key, newLocalVersion);
|
|
5517
|
+
const deletedValue = child.deletedValues.get(key);
|
|
5518
|
+
if (deletedValue !== void 0) {
|
|
5519
|
+
this.deletedValues.set(key, deletedValue);
|
|
5520
|
+
}
|
|
5521
|
+
if (child.originallyExisted.has(key)) {
|
|
5522
|
+
this.originallyExisted.add(key);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
this.localVersion = newLocalVersion;
|
|
5526
|
+
this.root.activeTransactions.delete(child);
|
|
5527
|
+
return null;
|
|
5528
|
+
} else {
|
|
5529
|
+
const newVersion = this.version + 1;
|
|
5530
|
+
if (child !== this) {
|
|
5531
|
+
const modifiedKeys = /* @__PURE__ */ new Set([...child.writeBuffer.keys(), ...child.deleteBuffer]);
|
|
5532
|
+
for (const key of modifiedKeys) {
|
|
5533
|
+
const versions = this.versionIndex.get(key);
|
|
5534
|
+
if (versions && versions.length > 0) {
|
|
5535
|
+
const lastVer = versions[versions.length - 1].version;
|
|
5536
|
+
if (lastVer > child.snapshotVersion) {
|
|
5537
|
+
return {
|
|
5538
|
+
error: `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`,
|
|
5539
|
+
conflict: {
|
|
5540
|
+
key,
|
|
5541
|
+
parent: await this.read(key),
|
|
5542
|
+
child: await child.read(key)
|
|
5543
|
+
}
|
|
5544
|
+
};
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
for (const [key, value] of child.writeBuffer) {
|
|
5550
|
+
this.writeBuffer.set(key, value);
|
|
5551
|
+
this.deleteBuffer.delete(key);
|
|
5552
|
+
if (child.createdKeys.has(key)) {
|
|
5553
|
+
this.createdKeys.add(key);
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
for (const key of child.deleteBuffer) {
|
|
5557
|
+
this.deleteBuffer.add(key);
|
|
5558
|
+
this.writeBuffer.delete(key);
|
|
5559
|
+
this.createdKeys.delete(key);
|
|
5560
|
+
const deletedValue = child.deletedValues.get(key);
|
|
5561
|
+
if (deletedValue !== void 0) {
|
|
5562
|
+
this.deletedValues.set(key, deletedValue);
|
|
5563
|
+
}
|
|
5564
|
+
if (child.originallyExisted.has(key)) {
|
|
5565
|
+
this.originallyExisted.add(key);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
for (const [key, value] of child.writeBuffer) {
|
|
5569
|
+
await this._diskWrite(key, value, newVersion);
|
|
5570
|
+
}
|
|
5571
|
+
for (const key of child.deleteBuffer) {
|
|
5572
|
+
await this._diskDelete(key, newVersion);
|
|
5573
|
+
}
|
|
5574
|
+
this.version = newVersion;
|
|
5575
|
+
this.root.activeTransactions.delete(child);
|
|
5576
|
+
this._cleanupDeletedCache();
|
|
5577
|
+
return null;
|
|
5578
|
+
}
|
|
5579
|
+
});
|
|
5580
|
+
}
|
|
5581
|
+
// --- Internal IO Helpers (Root Only) ---
|
|
5582
|
+
async _diskWrite(key, value, version) {
|
|
5583
|
+
const strategy = this.strategy;
|
|
5584
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5585
|
+
if (await strategy.exists(key)) {
|
|
5586
|
+
const currentVal = await strategy.read(key);
|
|
5587
|
+
if (!this.deletedCache.has(key)) this.deletedCache.set(key, []);
|
|
5588
|
+
this.deletedCache.get(key).push({
|
|
5589
|
+
value: currentVal,
|
|
5590
|
+
deletedAtVersion: version
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
await strategy.write(key, value);
|
|
5594
|
+
if (!this.versionIndex.has(key)) this.versionIndex.set(key, []);
|
|
5595
|
+
this.versionIndex.get(key).push({ version, exists: true });
|
|
5596
|
+
}
|
|
5597
|
+
async _diskRead(key, snapshotVersion) {
|
|
5598
|
+
const strategy = this.strategy;
|
|
5599
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5600
|
+
const versions = this.versionIndex.get(key);
|
|
5601
|
+
if (!versions) {
|
|
5602
|
+
return await strategy.exists(key) ? strategy.read(key) : null;
|
|
5603
|
+
}
|
|
5604
|
+
let targetVerObj = null;
|
|
5605
|
+
let nextVerObj = null;
|
|
5606
|
+
for (const v of versions) {
|
|
5607
|
+
if (v.version <= snapshotVersion) {
|
|
5608
|
+
targetVerObj = v;
|
|
5609
|
+
} else {
|
|
5610
|
+
nextVerObj = v;
|
|
5611
|
+
break;
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
if (!targetVerObj) {
|
|
5615
|
+
if (nextVerObj) {
|
|
5616
|
+
const cached2 = this.deletedCache.get(key);
|
|
5617
|
+
if (cached2) {
|
|
5618
|
+
const match = cached2.find((c) => c.deletedAtVersion === nextVerObj.version);
|
|
5619
|
+
if (match) return match.value;
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
return null;
|
|
5623
|
+
}
|
|
5624
|
+
if (!targetVerObj.exists) return null;
|
|
5625
|
+
if (!nextVerObj) {
|
|
5626
|
+
return strategy.read(key);
|
|
5627
|
+
}
|
|
5628
|
+
const cached = this.deletedCache.get(key);
|
|
5629
|
+
if (cached) {
|
|
5630
|
+
const match = cached.find((c) => c.deletedAtVersion === nextVerObj.version);
|
|
5631
|
+
if (match) return match.value;
|
|
5632
|
+
}
|
|
5633
|
+
return null;
|
|
5634
|
+
}
|
|
5635
|
+
async _diskExists(key, snapshotVersion) {
|
|
5636
|
+
const strategy = this.strategy;
|
|
5637
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5638
|
+
const versions = this.versionIndex.get(key);
|
|
5639
|
+
if (!versions) {
|
|
5640
|
+
return strategy.exists(key);
|
|
5641
|
+
}
|
|
5642
|
+
let targetVerObj = null;
|
|
5643
|
+
for (const v of versions) {
|
|
5644
|
+
if (v.version <= snapshotVersion) {
|
|
5645
|
+
targetVerObj = v;
|
|
5646
|
+
} else {
|
|
5647
|
+
break;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
if (!targetVerObj) return strategy.exists(key);
|
|
5651
|
+
return targetVerObj.exists;
|
|
5652
|
+
}
|
|
5653
|
+
async _diskDelete(key, snapshotVersion) {
|
|
5654
|
+
const strategy = this.strategy;
|
|
5655
|
+
if (!strategy) throw new Error("Root Transaction missing strategy");
|
|
5656
|
+
if (await strategy.exists(key)) {
|
|
5657
|
+
const currentVal = await strategy.read(key);
|
|
5658
|
+
if (!this.deletedCache.has(key)) this.deletedCache.set(key, []);
|
|
5659
|
+
this.deletedCache.get(key).push({
|
|
5660
|
+
value: currentVal,
|
|
5661
|
+
deletedAtVersion: snapshotVersion
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
await strategy.delete(key);
|
|
5665
|
+
if (!this.versionIndex.has(key)) this.versionIndex.set(key, []);
|
|
5666
|
+
this.versionIndex.get(key).push({ version: snapshotVersion, exists: false });
|
|
5667
|
+
}
|
|
5668
|
+
_cleanupDeletedCache() {
|
|
5669
|
+
if (this.deletedCache.size === 0) return;
|
|
5670
|
+
let minActiveVersion = this.version;
|
|
5671
|
+
if (this.activeTransactions.size > 0) {
|
|
5672
|
+
for (const tx of this.activeTransactions) {
|
|
5673
|
+
if (!tx.committed && tx.snapshotVersion < minActiveVersion) {
|
|
5674
|
+
minActiveVersion = tx.snapshotVersion;
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
for (const [key, cachedList] of this.deletedCache) {
|
|
5679
|
+
const remaining = cachedList.filter((item) => item.deletedAtVersion > minActiveVersion);
|
|
5680
|
+
if (remaining.length === 0) {
|
|
5681
|
+
this.deletedCache.delete(key);
|
|
5682
|
+
} else {
|
|
5683
|
+
this.deletedCache.set(key, remaining);
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
};
|
|
5688
|
+
|
|
4564
5689
|
// src/utils/numberToBytes.ts
|
|
4565
5690
|
function numberToBytes(value, buffer, offset = 0, length = buffer.length) {
|
|
4566
5691
|
if (length === 4) {
|
|
@@ -5921,7 +7046,7 @@ var PageManagerFactory = class _PageManagerFactory {
|
|
|
5921
7046
|
};
|
|
5922
7047
|
|
|
5923
7048
|
// src/core/DataplyAPI.ts
|
|
5924
|
-
var
|
|
7049
|
+
var import_node_fs3 = __toESM(require("node:fs"));
|
|
5925
7050
|
|
|
5926
7051
|
// node_modules/hookall/dist/esm/index.mjs
|
|
5927
7052
|
var HookallStore = class extends WeakMap {
|
|
@@ -6234,12 +7359,9 @@ var HookallSync = class _HookallSync {
|
|
|
6234
7359
|
}
|
|
6235
7360
|
};
|
|
6236
7361
|
|
|
6237
|
-
// src/core/
|
|
6238
|
-
var import_node_fs2 = __toESM(require("node:fs"));
|
|
6239
|
-
|
|
6240
|
-
// src/core/LogManager.ts
|
|
7362
|
+
// src/core/WALManager.ts
|
|
6241
7363
|
var import_node_fs = __toESM(require("node:fs"));
|
|
6242
|
-
var
|
|
7364
|
+
var WALManager = class {
|
|
6243
7365
|
fd = null;
|
|
6244
7366
|
walFilePath;
|
|
6245
7367
|
pageSize;
|
|
@@ -6252,6 +7374,9 @@ var LogManager = class {
|
|
|
6252
7374
|
* @param pageSize Page size
|
|
6253
7375
|
*/
|
|
6254
7376
|
constructor(walFilePath, pageSize) {
|
|
7377
|
+
if ((pageSize & pageSize - 1) !== 0) {
|
|
7378
|
+
throw new Error("Page size must be a power of 2");
|
|
7379
|
+
}
|
|
6255
7380
|
this.walFilePath = walFilePath;
|
|
6256
7381
|
this.pageSize = pageSize;
|
|
6257
7382
|
this.entrySize = 4 + pageSize;
|
|
@@ -6264,6 +7389,63 @@ var LogManager = class {
|
|
|
6264
7389
|
open() {
|
|
6265
7390
|
this.fd = import_node_fs.default.openSync(this.walFilePath, "a+");
|
|
6266
7391
|
}
|
|
7392
|
+
// ─────────────────────────────────────────────────────────────
|
|
7393
|
+
// High-level WAL operations (2-Phase Commit)
|
|
7394
|
+
// ─────────────────────────────────────────────────────────────
|
|
7395
|
+
/**
|
|
7396
|
+
* Performs recovery (Redo) using WAL logs.
|
|
7397
|
+
* Called during initialization, ensuring data is fully restored before operations start.
|
|
7398
|
+
* @param writePage Callback to write recovered pages to disk
|
|
7399
|
+
*/
|
|
7400
|
+
async recover(writePage) {
|
|
7401
|
+
this.open();
|
|
7402
|
+
const restoredPages = this.readAllSync();
|
|
7403
|
+
if (restoredPages.size === 0) {
|
|
7404
|
+
return;
|
|
7405
|
+
}
|
|
7406
|
+
const promises = [];
|
|
7407
|
+
for (const [pageId, data] of restoredPages) {
|
|
7408
|
+
if (pageId > 1e6) continue;
|
|
7409
|
+
try {
|
|
7410
|
+
const manager = new PageManagerFactory().getManager(data);
|
|
7411
|
+
if (!manager.verifyChecksum(data)) {
|
|
7412
|
+
console.warn(`[WALManager] Checksum verification failed for PageID ${pageId} during recovery. Ignoring changes.`);
|
|
7413
|
+
continue;
|
|
7414
|
+
}
|
|
7415
|
+
} catch (e) {
|
|
7416
|
+
console.warn(`[WALManager] Failed to verify checksum for PageID ${pageId} during recovery: ${e}. Ignoring changes.`);
|
|
7417
|
+
continue;
|
|
7418
|
+
}
|
|
7419
|
+
promises.push(writePage(pageId, data));
|
|
7420
|
+
}
|
|
7421
|
+
await Promise.all(promises);
|
|
7422
|
+
if (restoredPages.size > 0) {
|
|
7423
|
+
await this.clear();
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
/**
|
|
7427
|
+
* WAL에 페이지 데이터를 기록합니다 (Phase 1: Prepare).
|
|
7428
|
+
* @param dirtyPages 변경된 페이지들 (pageId -> data)
|
|
7429
|
+
*/
|
|
7430
|
+
async prepareCommit(dirtyPages) {
|
|
7431
|
+
if (dirtyPages.size === 0) {
|
|
7432
|
+
return;
|
|
7433
|
+
}
|
|
7434
|
+
await this.append(dirtyPages);
|
|
7435
|
+
}
|
|
7436
|
+
/**
|
|
7437
|
+
* WAL에 커밋 마커를 기록하고 로그를 정리합니다 (Phase 2: Finalize).
|
|
7438
|
+
* @param hasActiveTransactions 아직 활성 트랜잭션이 있는지 여부
|
|
7439
|
+
*/
|
|
7440
|
+
async finalizeCommit(hasActiveTransactions) {
|
|
7441
|
+
await this.writeCommitMarker();
|
|
7442
|
+
if (!hasActiveTransactions) {
|
|
7443
|
+
await this.clear();
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
// ─────────────────────────────────────────────────────────────
|
|
7447
|
+
// Low-level WAL operations
|
|
7448
|
+
// ─────────────────────────────────────────────────────────────
|
|
6267
7449
|
/**
|
|
6268
7450
|
* Appends changed pages to the log file.
|
|
6269
7451
|
* Records them sorted by page ID.
|
|
@@ -6317,7 +7499,7 @@ var LogManager = class {
|
|
|
6317
7499
|
}
|
|
6318
7500
|
/**
|
|
6319
7501
|
* Reads the log file to recover the page map.
|
|
6320
|
-
* Runs synchronously as it is called
|
|
7502
|
+
* Runs synchronously as it is called during initialization.
|
|
6321
7503
|
* Only returns pages from committed transactions (ended with a commit marker).
|
|
6322
7504
|
* @returns Recovered page map
|
|
6323
7505
|
*/
|
|
@@ -6372,173 +7554,15 @@ var LogManager = class {
|
|
|
6372
7554
|
}
|
|
6373
7555
|
};
|
|
6374
7556
|
|
|
6375
|
-
// src/core/VirtualFileSystem.ts
|
|
6376
|
-
var VirtualFileSystem = class {
|
|
6377
|
-
/** Track logical file size */
|
|
6378
|
-
fileSize;
|
|
6379
|
-
/** Page size */
|
|
6380
|
-
pageSize;
|
|
6381
|
-
/** File handle */
|
|
6382
|
-
fileHandle;
|
|
6383
|
-
/** WAL Log Manager */
|
|
6384
|
-
logManager;
|
|
6385
|
-
constructor(fileHandle, pageSize, pageCacheCapacity, walPath) {
|
|
6386
|
-
if ((pageSize & pageSize - 1) !== 0) {
|
|
6387
|
-
throw new Error("Page size must be a power of 2");
|
|
6388
|
-
}
|
|
6389
|
-
this.fileHandle = fileHandle;
|
|
6390
|
-
this.pageSize = pageSize;
|
|
6391
|
-
this.fileSize = import_node_fs2.default.fstatSync(fileHandle).size;
|
|
6392
|
-
if (walPath) {
|
|
6393
|
-
this.logManager = new LogManager(walPath, pageSize);
|
|
6394
|
-
}
|
|
6395
|
-
}
|
|
6396
|
-
/**
|
|
6397
|
-
* Performs recovery (Redo) using WAL logs.
|
|
6398
|
-
* Called during initialization (DataplyAPI.init), ensuring data is fully restored before operations start.
|
|
6399
|
-
*/
|
|
6400
|
-
async recover() {
|
|
6401
|
-
if (!this.logManager) return;
|
|
6402
|
-
this.logManager.open();
|
|
6403
|
-
const restoredPages = this.logManager.readAllSync();
|
|
6404
|
-
if (restoredPages.size === 0) {
|
|
6405
|
-
return;
|
|
6406
|
-
}
|
|
6407
|
-
const promises = [];
|
|
6408
|
-
for (const [pageId, data] of restoredPages) {
|
|
6409
|
-
if (pageId > 1e6) continue;
|
|
6410
|
-
try {
|
|
6411
|
-
const manager = new PageManagerFactory().getManager(data);
|
|
6412
|
-
if (!manager.verifyChecksum(data)) {
|
|
6413
|
-
console.warn(`[VFS] Checksum verification failed for PageID ${pageId} during recovery. Ignoring changes.`);
|
|
6414
|
-
continue;
|
|
6415
|
-
}
|
|
6416
|
-
} catch (e) {
|
|
6417
|
-
console.warn(`[VFS] Failed to verify checksum for PageID ${pageId} during recovery: ${e}. Ignoring changes.`);
|
|
6418
|
-
continue;
|
|
6419
|
-
}
|
|
6420
|
-
promises.push(this._writeAsync(
|
|
6421
|
-
this.fileHandle,
|
|
6422
|
-
data,
|
|
6423
|
-
0,
|
|
6424
|
-
this.pageSize,
|
|
6425
|
-
pageId * this.pageSize
|
|
6426
|
-
));
|
|
6427
|
-
const endPos = (pageId + 1) * this.pageSize;
|
|
6428
|
-
if (endPos > this.fileSize) {
|
|
6429
|
-
this.fileSize = endPos;
|
|
6430
|
-
}
|
|
6431
|
-
}
|
|
6432
|
-
await Promise.all(promises);
|
|
6433
|
-
if (this.logManager && restoredPages.size > 0) {
|
|
6434
|
-
await this.logManager.clear();
|
|
6435
|
-
}
|
|
6436
|
-
}
|
|
6437
|
-
/**
|
|
6438
|
-
* WAL에 페이지 데이터를 기록합니다 (Phase 1).
|
|
6439
|
-
* @param dirtyPages 변경된 페이지들 (pageId -> data)
|
|
6440
|
-
*/
|
|
6441
|
-
async prepareCommitWAL(dirtyPages) {
|
|
6442
|
-
if (!this.logManager || dirtyPages.size === 0) {
|
|
6443
|
-
return;
|
|
6444
|
-
}
|
|
6445
|
-
await this.logManager.append(dirtyPages);
|
|
6446
|
-
}
|
|
6447
|
-
/**
|
|
6448
|
-
* WAL에 커밋 마커를 기록하고 로그를 정리합니다 (Phase 2).
|
|
6449
|
-
* @param hasActiveTransactions 아직 활성 트랜잭션이 있는지 여부
|
|
6450
|
-
*/
|
|
6451
|
-
async finalizeCommitWAL(hasActiveTransactions) {
|
|
6452
|
-
if (!this.logManager) {
|
|
6453
|
-
return;
|
|
6454
|
-
}
|
|
6455
|
-
await this.logManager.writeCommitMarker();
|
|
6456
|
-
if (!hasActiveTransactions) {
|
|
6457
|
-
await this.logManager.clear();
|
|
6458
|
-
}
|
|
6459
|
-
}
|
|
6460
|
-
/**
|
|
6461
|
-
* 디스크에서 페이지를 읽습니다.
|
|
6462
|
-
* @param pageId Page ID
|
|
6463
|
-
* @returns Page data
|
|
6464
|
-
*/
|
|
6465
|
-
async readPage(pageId) {
|
|
6466
|
-
const buffer = new Uint8Array(this.pageSize);
|
|
6467
|
-
const pageStartPos = pageId * this.pageSize;
|
|
6468
|
-
if (pageStartPos >= this.fileSize) {
|
|
6469
|
-
return buffer;
|
|
6470
|
-
}
|
|
6471
|
-
await this._readAsync(this.fileHandle, buffer, 0, this.pageSize, pageStartPos);
|
|
6472
|
-
return buffer;
|
|
6473
|
-
}
|
|
6474
|
-
/**
|
|
6475
|
-
* 디스크에 페이지를 씁니다.
|
|
6476
|
-
* @param pageId Page ID
|
|
6477
|
-
* @param data Page data
|
|
6478
|
-
*/
|
|
6479
|
-
async writePage(pageId, data) {
|
|
6480
|
-
const pageStartPos = pageId * this.pageSize;
|
|
6481
|
-
if (pageStartPos + this.pageSize > 512 * 1024 * 1024) {
|
|
6482
|
-
throw new Error(`[Safety Limit] File write exceeds 512MB limit at position ${pageStartPos}`);
|
|
6483
|
-
}
|
|
6484
|
-
await this._writeAsync(this.fileHandle, data, 0, this.pageSize, pageStartPos);
|
|
6485
|
-
const endPosition = pageStartPos + this.pageSize;
|
|
6486
|
-
if (endPosition > this.fileSize) {
|
|
6487
|
-
this.fileSize = endPosition;
|
|
6488
|
-
}
|
|
6489
|
-
}
|
|
6490
|
-
/**
|
|
6491
|
-
* 현재 파일 크기 반환
|
|
6492
|
-
*/
|
|
6493
|
-
getFileSize() {
|
|
6494
|
-
return this.fileSize;
|
|
6495
|
-
}
|
|
6496
|
-
/**
|
|
6497
|
-
* Closes the file.
|
|
6498
|
-
*/
|
|
6499
|
-
async close() {
|
|
6500
|
-
if (this.logManager) {
|
|
6501
|
-
this.logManager.close();
|
|
6502
|
-
}
|
|
6503
|
-
}
|
|
6504
|
-
_readAsync(handle, buffer, offset, length, position) {
|
|
6505
|
-
return new Promise((resolve, reject) => {
|
|
6506
|
-
import_node_fs2.default.read(handle, buffer, offset, length, position, (err, bytesRead) => {
|
|
6507
|
-
if (err) return reject(err);
|
|
6508
|
-
resolve(bytesRead);
|
|
6509
|
-
});
|
|
6510
|
-
});
|
|
6511
|
-
}
|
|
6512
|
-
_writeAsync(handle, buffer, offset, length, position) {
|
|
6513
|
-
if (position + length > 512 * 1024 * 1024) {
|
|
6514
|
-
return Promise.reject(new Error(`[Safety Limit] File write exceeds 512MB limit at position ${position}`));
|
|
6515
|
-
}
|
|
6516
|
-
return new Promise((resolve, reject) => {
|
|
6517
|
-
import_node_fs2.default.write(handle, buffer, offset, length, position, (err, bytesWritten) => {
|
|
6518
|
-
if (err) return reject(err);
|
|
6519
|
-
resolve(bytesWritten);
|
|
6520
|
-
});
|
|
6521
|
-
});
|
|
6522
|
-
}
|
|
6523
|
-
};
|
|
6524
|
-
|
|
6525
|
-
// src/core/PageMVCCStrategy.ts
|
|
6526
|
-
var import_node_fs3 = __toESM(require("node:fs"));
|
|
6527
|
-
|
|
6528
|
-
// node_modules/mvcc-api/dist/esm/index.mjs
|
|
6529
|
-
var MVCCStrategy2 = class {
|
|
6530
|
-
};
|
|
6531
|
-
var AsyncMVCCStrategy2 = class extends MVCCStrategy2 {
|
|
6532
|
-
};
|
|
6533
|
-
|
|
6534
7557
|
// src/core/PageMVCCStrategy.ts
|
|
7558
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
6535
7559
|
var PageMVCCStrategy = class extends AsyncMVCCStrategy2 {
|
|
6536
7560
|
constructor(fileHandle, pageSize, cacheCapacity) {
|
|
6537
7561
|
super();
|
|
6538
7562
|
this.fileHandle = fileHandle;
|
|
6539
7563
|
this.pageSize = pageSize;
|
|
6540
7564
|
this.cache = new LRUMap2(cacheCapacity);
|
|
6541
|
-
this.fileSize =
|
|
7565
|
+
this.fileSize = import_node_fs2.default.fstatSync(fileHandle).size;
|
|
6542
7566
|
}
|
|
6543
7567
|
/** LRU 캐시 (페이지 ID -> 페이지 데이터) */
|
|
6544
7568
|
cache;
|
|
@@ -6621,7 +7645,7 @@ var PageMVCCStrategy = class extends AsyncMVCCStrategy2 {
|
|
|
6621
7645
|
// ─────────────────────────────────────────────────────────────
|
|
6622
7646
|
_readFromDisk(buffer, position) {
|
|
6623
7647
|
return new Promise((resolve, reject) => {
|
|
6624
|
-
|
|
7648
|
+
import_node_fs2.default.read(this.fileHandle, buffer, 0, this.pageSize, position, (err, bytesRead) => {
|
|
6625
7649
|
if (err) return reject(err);
|
|
6626
7650
|
resolve(bytesRead);
|
|
6627
7651
|
});
|
|
@@ -6629,7 +7653,7 @@ var PageMVCCStrategy = class extends AsyncMVCCStrategy2 {
|
|
|
6629
7653
|
}
|
|
6630
7654
|
_writeToDisk(buffer, position) {
|
|
6631
7655
|
return new Promise((resolve, reject) => {
|
|
6632
|
-
|
|
7656
|
+
import_node_fs2.default.write(this.fileHandle, buffer, 0, this.pageSize, position, (err, bytesWritten) => {
|
|
6633
7657
|
if (err) return reject(err);
|
|
6634
7658
|
resolve(bytesWritten);
|
|
6635
7659
|
});
|
|
@@ -6650,20 +7674,24 @@ var PageFileSystem = class {
|
|
|
6650
7674
|
this.pageSize = pageSize;
|
|
6651
7675
|
this.pageCacheCapacity = pageCacheCapacity;
|
|
6652
7676
|
this.walPath = walPath;
|
|
6653
|
-
this.
|
|
7677
|
+
this.walManager = walPath ? new WALManager(walPath, pageSize) : null;
|
|
6654
7678
|
this.pageManagerFactory = new PageManagerFactory();
|
|
6655
7679
|
this.pageStrategy = new PageMVCCStrategy(fileHandle, pageSize, pageCacheCapacity);
|
|
6656
7680
|
}
|
|
6657
7681
|
pageFactory = new PageManagerFactory();
|
|
6658
|
-
|
|
7682
|
+
walManager;
|
|
6659
7683
|
pageManagerFactory;
|
|
6660
7684
|
pageStrategy;
|
|
6661
7685
|
/**
|
|
6662
7686
|
* Initializes the page file system.
|
|
6663
|
-
* Performs
|
|
7687
|
+
* Performs WAL recovery if necessary.
|
|
6664
7688
|
*/
|
|
6665
7689
|
async init() {
|
|
6666
|
-
|
|
7690
|
+
if (this.walManager) {
|
|
7691
|
+
await this.walManager.recover(async (pageId, data) => {
|
|
7692
|
+
await this.pageStrategy.write(pageId, data);
|
|
7693
|
+
});
|
|
7694
|
+
}
|
|
6667
7695
|
}
|
|
6668
7696
|
/**
|
|
6669
7697
|
* Returns the page strategy for transaction use.
|
|
@@ -6709,10 +7737,10 @@ var PageFileSystem = class {
|
|
|
6709
7737
|
await this.setPage(currentBitmapPageId, targetBitmapPage, tx);
|
|
6710
7738
|
}
|
|
6711
7739
|
/**
|
|
6712
|
-
*
|
|
7740
|
+
* WAL Manager 인스턴스를 반환합니다.
|
|
6713
7741
|
*/
|
|
6714
|
-
get
|
|
6715
|
-
return this.
|
|
7742
|
+
get wal() {
|
|
7743
|
+
return this.walManager;
|
|
6716
7744
|
}
|
|
6717
7745
|
/**
|
|
6718
7746
|
* 페이지 Strategy를 반환합니다.
|
|
@@ -6947,14 +7975,18 @@ var PageFileSystem = class {
|
|
|
6947
7975
|
* @param dirtyPages 변경된 페이지들
|
|
6948
7976
|
*/
|
|
6949
7977
|
async commitToWAL(dirtyPages) {
|
|
6950
|
-
|
|
6951
|
-
|
|
7978
|
+
if (this.walManager) {
|
|
7979
|
+
await this.walManager.prepareCommit(dirtyPages);
|
|
7980
|
+
await this.walManager.finalizeCommit(false);
|
|
7981
|
+
}
|
|
6952
7982
|
}
|
|
6953
7983
|
/**
|
|
6954
7984
|
* Closes the page file system.
|
|
6955
7985
|
*/
|
|
6956
7986
|
async close() {
|
|
6957
|
-
|
|
7987
|
+
if (this.walManager) {
|
|
7988
|
+
this.walManager.close();
|
|
7989
|
+
}
|
|
6958
7990
|
}
|
|
6959
7991
|
};
|
|
6960
7992
|
|
|
@@ -7835,7 +8867,7 @@ var DataplyAPI = class {
|
|
|
7835
8867
|
this.file = file;
|
|
7836
8868
|
this.hook = useHookall(this);
|
|
7837
8869
|
this.options = this.verboseOptions(options);
|
|
7838
|
-
this.isNewlyCreated = !
|
|
8870
|
+
this.isNewlyCreated = !import_node_fs3.default.existsSync(file);
|
|
7839
8871
|
this.fileHandle = this.createOrOpen(file, this.options);
|
|
7840
8872
|
this.pfs = new PageFileSystem(
|
|
7841
8873
|
this.fileHandle,
|
|
@@ -7884,7 +8916,7 @@ var DataplyAPI = class {
|
|
|
7884
8916
|
verifyFormat(fileHandle) {
|
|
7885
8917
|
const size = MetadataPageManager.CONSTANT.OFFSET_MAGIC_STRING + MetadataPageManager.CONSTANT.MAGIC_STRING.length;
|
|
7886
8918
|
const metadataPage = new Uint8Array(size);
|
|
7887
|
-
|
|
8919
|
+
import_node_fs3.default.readSync(fileHandle, metadataPage, 0, size, 0);
|
|
7888
8920
|
if (!MetadataPageManager.IsMetadataPage(metadataPage)) {
|
|
7889
8921
|
return false;
|
|
7890
8922
|
}
|
|
@@ -7946,7 +8978,7 @@ var DataplyAPI = class {
|
|
|
7946
8978
|
-1,
|
|
7947
8979
|
options.pageSize - DataPageManager.CONSTANT.SIZE_PAGE_HEADER
|
|
7948
8980
|
);
|
|
7949
|
-
|
|
8981
|
+
import_node_fs3.default.appendFileSync(fileHandle, new Uint8Array([
|
|
7950
8982
|
...metadataPage,
|
|
7951
8983
|
...bitmapPage,
|
|
7952
8984
|
...dataPage
|
|
@@ -7963,18 +8995,18 @@ var DataplyAPI = class {
|
|
|
7963
8995
|
if (options.pageCacheCapacity < 100) {
|
|
7964
8996
|
throw new Error("Page cache capacity must be at least 100");
|
|
7965
8997
|
}
|
|
7966
|
-
if (!
|
|
8998
|
+
if (!import_node_fs3.default.existsSync(file)) {
|
|
7967
8999
|
if (options.pageSize < 4096) {
|
|
7968
9000
|
throw new Error("Page size must be at least 4096 bytes");
|
|
7969
9001
|
}
|
|
7970
|
-
fileHandle =
|
|
9002
|
+
fileHandle = import_node_fs3.default.openSync(file, "w+");
|
|
7971
9003
|
this.initializeFile(file, fileHandle, options);
|
|
7972
9004
|
} else {
|
|
7973
|
-
fileHandle =
|
|
9005
|
+
fileHandle = import_node_fs3.default.openSync(file, "r+");
|
|
7974
9006
|
const buffer = new Uint8Array(
|
|
7975
9007
|
MetadataPageManager.CONSTANT.OFFSET_PAGE_SIZE + MetadataPageManager.CONSTANT.SIZE_PAGE_SIZE
|
|
7976
9008
|
);
|
|
7977
|
-
|
|
9009
|
+
import_node_fs3.default.readSync(fileHandle, buffer);
|
|
7978
9010
|
const metadataManager = new MetadataPageManager();
|
|
7979
9011
|
if (metadataManager.isMetadataPage(buffer)) {
|
|
7980
9012
|
const storedPageSize = metadataManager.getPageSize(buffer);
|
|
@@ -8169,7 +9201,7 @@ var DataplyAPI = class {
|
|
|
8169
9201
|
}
|
|
8170
9202
|
return this.hook.trigger("close", void 0, async () => {
|
|
8171
9203
|
await this.pfs.close();
|
|
8172
|
-
|
|
9204
|
+
import_node_fs3.default.closeSync(this.fileHandle);
|
|
8173
9205
|
});
|
|
8174
9206
|
}
|
|
8175
9207
|
};
|
|
@@ -8301,6 +9333,8 @@ var GlobalTransaction = class {
|
|
|
8301
9333
|
};
|
|
8302
9334
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8303
9335
|
0 && (module.exports = {
|
|
9336
|
+
AsyncMVCCStrategy,
|
|
9337
|
+
AsyncMVCCTransaction,
|
|
8304
9338
|
BPTreeAsync,
|
|
8305
9339
|
BPTreeAsyncTransaction,
|
|
8306
9340
|
BPTreeSync,
|
|
@@ -8318,6 +9352,8 @@ var GlobalTransaction = class {
|
|
|
8318
9352
|
IndexPageManager,
|
|
8319
9353
|
InvertedWeakMap,
|
|
8320
9354
|
LRUMap,
|
|
9355
|
+
MVCCStrategy,
|
|
9356
|
+
MVCCTransaction,
|
|
8321
9357
|
MetadataPageManager,
|
|
8322
9358
|
NumericComparator,
|
|
8323
9359
|
OverflowPageManager,
|
|
@@ -8327,6 +9363,8 @@ var GlobalTransaction = class {
|
|
|
8327
9363
|
SerializeStrategyAsync,
|
|
8328
9364
|
SerializeStrategySync,
|
|
8329
9365
|
StringComparator,
|
|
9366
|
+
SyncMVCCStrategy,
|
|
9367
|
+
SyncMVCCTransaction,
|
|
8330
9368
|
Transaction,
|
|
8331
9369
|
UnknownPageManager,
|
|
8332
9370
|
ValueComparator
|