mvcc-api 1.1.0 → 1.2.0
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 +148 -97
- package/dist/cjs/index.cjs +191 -48
- package/dist/esm/index.mjs +191 -48
- package/dist/types/core/async/Transaction.d.ts +6 -2
- package/dist/types/core/base/Transaction.d.ts +18 -11
- package/dist/types/core/sync/Transaction.d.ts +6 -2
- package/dist/types/types/index.d.ts +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,149 +5,200 @@
|
|
|
5
5
|
|
|
6
6
|
Multiversion Concurrency Control (MVCC) API for TypeScript.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
It implements Snapshot Isolation and supports synchronous/asynchronous operations and flexible nested transactions.
|
|
9
9
|
|
|
10
|
-
## Features
|
|
10
|
+
## Key Features
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
| Feature | Description |
|
|
13
|
+
| :--- | :--- |
|
|
14
|
+
| **MVCC Support** | Prevents blocking between reads/writes via Snapshot Isolation |
|
|
15
|
+
| **Strict Isolation** | Children can only see data committed by their parents |
|
|
16
|
+
| **Reusable Root** | Root transaction can be committed multiple times |
|
|
17
|
+
| **Conflict Detection** | Automatic conflict detection between transactions modifying the same key |
|
|
18
|
+
| **Result Tracking** | Returns list of created/updated/deleted keys and data upon commit/rollback |
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Why `mvcc-api`?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
It easily and powerfully solves complex concurrency problems that are difficult to handle with simple file I/O or data manipulation.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
1. **High-Performance Non-blocking Reads**
|
|
25
|
+
* Read operations do not wait even if write operations are in progress.
|
|
26
|
+
* Snapshot Isolation always provides data from a consistent point in time.
|
|
27
|
+
|
|
28
|
+
2. **Perfect Atomicity (All-or-Nothing)**
|
|
29
|
+
* Bundles changes to multiple files or data into a single transaction.
|
|
30
|
+
* If it fails midway, all changes are cleanly cancelled. No worries about data corruption due to partial updates.
|
|
31
|
+
|
|
32
|
+
3. **Flexible Storage Extension**
|
|
33
|
+
* You can apply MVCC features to anything—file systems, in-memory objects, local storage, etc.—just by implementing the `Strategy` interface.
|
|
34
|
+
* Business logic and storage logic can be perfectly separated.
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
4. **Improved Development Productivity**
|
|
37
|
+
* No need to write complex synchronization code yourself; write safe code with just intuitive `api.read()`, `api.write()`, and `commit()`.
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} from 'https://cdn.jsdelivr.net/npm/mvcc-api@1/+esm'
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install mvcc-api
|
|
34
43
|
```
|
|
35
44
|
|
|
36
45
|
## Usage
|
|
37
46
|
|
|
38
|
-
### 1. Implement
|
|
39
|
-
|
|
40
|
-
Define how data is stored by extending `MVCCStrategy`.
|
|
47
|
+
### 1. Implement Strategy
|
|
41
48
|
|
|
42
49
|
```typescript
|
|
43
50
|
import fs from 'node:fs'
|
|
44
51
|
import { AsyncMVCCStrategy } from 'mvcc-api'
|
|
45
52
|
|
|
46
|
-
export class
|
|
47
|
-
async read(key: string)
|
|
53
|
+
export class FileStrategy extends AsyncMVCCStrategy<string, string> {
|
|
54
|
+
async read(key: string) {
|
|
48
55
|
return fs.promises.readFile(key, 'utf-8')
|
|
49
56
|
}
|
|
50
|
-
async write(key: string, value: string)
|
|
51
|
-
await fs.promises.writeFile(key, value
|
|
57
|
+
async write(key: string, value: string) {
|
|
58
|
+
await fs.promises.writeFile(key, value)
|
|
52
59
|
}
|
|
53
|
-
async delete(key: string)
|
|
60
|
+
async delete(key: string) {
|
|
54
61
|
await fs.promises.unlink(key)
|
|
55
62
|
}
|
|
56
|
-
async exists(key: string)
|
|
63
|
+
async exists(key: string) {
|
|
57
64
|
return fs.existsSync(key)
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
```
|
|
61
68
|
|
|
62
|
-
### 2.
|
|
63
|
-
|
|
64
|
-
Initialize a root transaction with your strategy. You can then create nested transactions for isolated work.
|
|
69
|
+
### 2. Execute Transaction
|
|
65
70
|
|
|
66
71
|
```typescript
|
|
67
72
|
import { AsyncMVCCTransaction } from 'mvcc-api'
|
|
68
|
-
import { AsyncFileStrategy } from './AsyncFileStrategy'
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Create a Root Transaction (it can be committed multiple times)
|
|
73
|
-
const root = new AsyncMVCCTransaction(strategy)
|
|
74
|
+
const root = new AsyncMVCCTransaction(new FileStrategy())
|
|
75
|
+
const tx = root.createNested()
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
tx.create('new.json', '{}') // Create new key
|
|
78
|
+
tx.write('config.json', '{"v":2}') // Update existing key
|
|
79
|
+
tx.delete('old.json') // Delete key
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const data = await tx.read('data.json')
|
|
83
|
-
|
|
84
|
-
// Commit merges changes up to the parent (Root)
|
|
85
|
-
await tx.commit()
|
|
86
|
-
|
|
87
|
-
// Root must commit to persist to storage
|
|
88
|
-
await root.commit()
|
|
89
|
-
} catch (err) {
|
|
90
|
-
tx.rollback()
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
81
|
+
const result = await tx.commit()
|
|
82
|
+
// result.created = [{ key: 'new.json', data: '{}' }]
|
|
83
|
+
// result.updated = [{ key: 'config.json', data: '{"v":2}' }]
|
|
84
|
+
// result.deleted = [{ key: 'old.json', data: '<value before delete>' }]
|
|
94
85
|
|
|
95
|
-
|
|
86
|
+
await root.commit() // Persist to storage
|
|
87
|
+
```
|
|
96
88
|
|
|
97
|
-
|
|
89
|
+
## Visibility Rules
|
|
98
90
|
|
|
99
91
|
```mermaid
|
|
100
92
|
sequenceDiagram
|
|
101
|
-
participant
|
|
102
|
-
participant
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
S->>R: Initial State
|
|
107
|
-
R->>R: write('k1', 'v1')
|
|
108
|
-
Note right of R: Buffer: k1=v1 (Uncommitted)
|
|
93
|
+
participant P as Parent
|
|
94
|
+
participant C as Child
|
|
95
|
+
|
|
96
|
+
Note over P: "key": "A" (Committed)
|
|
97
|
+
P->>P: write("key", "B") (Uncommitted)
|
|
109
98
|
|
|
110
|
-
rect rgb(240,
|
|
111
|
-
Note over
|
|
112
|
-
|
|
113
|
-
Note right of
|
|
99
|
+
rect rgb(240,240,250)
|
|
100
|
+
Note over C: Child Created
|
|
101
|
+
C->>P: read("key") → "A"
|
|
102
|
+
Note right of C: Cannot see parent's<br/>uncommitted data
|
|
114
103
|
end
|
|
104
|
+
```
|
|
115
105
|
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
> [!IMPORTANT]
|
|
107
|
+
> **Visibility Rules**
|
|
108
|
+
> - Transactions can always see their own changes.
|
|
109
|
+
> - Children can only see **committed data** at the time of creation.
|
|
110
|
+
> - Snapshots are maintained even if external commits occur after creation.
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
## Conflict Detection
|
|
113
|
+
|
|
114
|
+
Conflicts occur upon commit if transactions have modified the same key.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const parent = root.createNested()
|
|
118
|
+
const child = parent.createNested()
|
|
119
|
+
|
|
120
|
+
parent.write('shared', 'parent') // Parent modifies after child creation
|
|
121
|
+
child.write('shared', 'child') // Child modifies same key
|
|
122
|
+
|
|
123
|
+
const result = child.commit()
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
console.log(result.error) // "Commit conflict: Key 'shared' was modified..."
|
|
126
|
+
}
|
|
124
127
|
```
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
| Parent Mod | Child Mod | Result |
|
|
130
|
+
|:---:|:---:|:---:|
|
|
131
|
+
| `A` | `A` | ❌ Conflict |
|
|
132
|
+
| `A` | `B` | ✅ Success |
|
|
133
|
+
|
|
134
|
+
> [!TIP]
|
|
135
|
+
> **No Conflict on Different Keys**
|
|
136
|
+
>
|
|
137
|
+
> MVCC detects conflicts on a **Key basis**. Sibling transactions can both commit successfully if they modify **different keys**.
|
|
138
|
+
>
|
|
139
|
+
> ```typescript
|
|
140
|
+
> const t1 = root.createNested()
|
|
141
|
+
> const t2 = root.createNested()
|
|
142
|
+
>
|
|
143
|
+
> t1.create('Key1', 'data')
|
|
144
|
+
> t2.create('Key2', 'data') // Different key
|
|
145
|
+
>
|
|
146
|
+
> t1.commit() // Success
|
|
147
|
+
> t2.commit() // Success
|
|
148
|
+
> ```
|
|
149
|
+
|
|
150
|
+
## Result Accumulation
|
|
151
|
+
|
|
152
|
+
When a child commits, the results are accumulated in the parent.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const b = a.createNested()
|
|
156
|
+
const c = b.createNested()
|
|
157
|
+
|
|
158
|
+
c.create('C', 'val')
|
|
159
|
+
const cResult = c.commit()
|
|
160
|
+
// cResult.created = [{ key: 'C', data: 'val' }]
|
|
161
|
+
|
|
162
|
+
b.create('B', 'val')
|
|
163
|
+
const bResult = b.commit()
|
|
164
|
+
// bResult.created = [{ key: 'C', data: 'val' }, { key: 'B', data: 'val' }]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> [!NOTE]
|
|
168
|
+
> **Changes from rolled-back children are not passed to the parent.**
|
|
130
169
|
|
|
131
170
|
## API Reference
|
|
132
171
|
|
|
133
|
-
### `MVCCTransaction<S, K, T>`
|
|
134
|
-
- **Constructor**: `new SyncMVCCTransaction(strategy?)` or `new AsyncMVCCTransaction(strategy?)`
|
|
135
|
-
- Pass a `strategy` only for the Root transaction.
|
|
136
|
-
- `read(key: K)`: Reads value from local buffer or globally committed snapshot.
|
|
137
|
-
- `write(key: K, value: T)`: Buffers a write operation.
|
|
138
|
-
- `delete(key: K)`: Buffers a delete operation.
|
|
139
|
-
- `commit()`:
|
|
140
|
-
- **Root**: Persists all buffered changes to storage and resets local buffers. The Root remains open for further operations.
|
|
141
|
-
- **Nested**: Merges changes into the parent's buffer and closes the transaction.
|
|
142
|
-
- `rollback()`: Discards all local changes and closes the transaction (if Nested).
|
|
143
|
-
- `createNested()`: Creates a new child transaction with a snapshot of the current globally committed state.
|
|
144
|
-
|
|
145
|
-
### `MVCCStrategy<K, T>` (Abstract)
|
|
146
|
-
- `read(key: K)`
|
|
147
|
-
- `write(key: K, value: T)`
|
|
148
|
-
- `delete(key: K)`
|
|
149
|
-
- `exists(key: K)`
|
|
172
|
+
### `MVCCTransaction<S, K, T>`
|
|
150
173
|
|
|
151
|
-
|
|
174
|
+
| Method | Description | Return Value |
|
|
175
|
+
| :--- | :--- | :--- |
|
|
176
|
+
| `create(key, value)` | Create new key-value | `this` |
|
|
177
|
+
| `write(key, value)` | Update existing key | `this` |
|
|
178
|
+
| `delete(key)` | Delete key | `this` |
|
|
179
|
+
| `read(key)` | Read value | `T \| null` |
|
|
180
|
+
| `commit()` | Apply changes | `TransactionResult<K, T>` |
|
|
181
|
+
| `rollback()` | Discard changes | `TransactionResult<K, T>` |
|
|
182
|
+
| `createNested()` | Create child transaction | `MVCCTransaction` |
|
|
183
|
+
|
|
184
|
+
### `TransactionResult<K, T>`
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
type TransactionEntry<K, T> = { key: K, data: T }
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
success: boolean // Success status
|
|
191
|
+
error?: string // Error message on failure (e.g. conflict)
|
|
192
|
+
created: TransactionEntry[] // Keys and values created via create()
|
|
193
|
+
updated: TransactionEntry[] // Keys and values updated via write()
|
|
194
|
+
deleted: TransactionEntry[] // Keys deleted via delete() and their previous values
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
152
199
|
|
|
200
|
+
`mvcc-api` aims to help anyone easily use complex concurrency control.
|
|
201
|
+
Bug reports, feature suggestions, and PRs are always welcome! Please feel free to leave your feedback via GitHub Issues.
|
|
202
|
+
|
|
203
|
+
## License
|
|
153
204
|
MIT
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -38,6 +38,10 @@ var MVCCTransaction = class {
|
|
|
38
38
|
snapshotLocalVersion;
|
|
39
39
|
writeBuffer;
|
|
40
40
|
deleteBuffer;
|
|
41
|
+
createdKeys;
|
|
42
|
+
// create()로 생성된 키 추적
|
|
43
|
+
deletedValues;
|
|
44
|
+
// delete 시 삭제 전 값 저장
|
|
41
45
|
// Nested Transaction Properties
|
|
42
46
|
parent;
|
|
43
47
|
localVersion;
|
|
@@ -55,6 +59,8 @@ var MVCCTransaction = class {
|
|
|
55
59
|
this.snapshotVersion = snapshotVersion ?? 0;
|
|
56
60
|
this.writeBuffer = /* @__PURE__ */ new Map();
|
|
57
61
|
this.deleteBuffer = /* @__PURE__ */ new Set();
|
|
62
|
+
this.createdKeys = /* @__PURE__ */ new Set();
|
|
63
|
+
this.deletedValues = /* @__PURE__ */ new Map();
|
|
58
64
|
this.committed = false;
|
|
59
65
|
this.parent = parent;
|
|
60
66
|
this.keyVersions = /* @__PURE__ */ new Map();
|
|
@@ -75,59 +81,58 @@ var MVCCTransaction = class {
|
|
|
75
81
|
isRoot() {
|
|
76
82
|
return !this.parent;
|
|
77
83
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @param key The key to create.
|
|
82
|
-
* @param value The value to store.
|
|
83
|
-
* @returns The transaction instance for chaining.
|
|
84
|
-
*/
|
|
85
|
-
create(key, value) {
|
|
86
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
84
|
+
// --- Internal buffer manipulation helpers ---
|
|
85
|
+
_bufferCreate(key, value) {
|
|
86
|
+
this.localVersion++;
|
|
87
87
|
this.writeBuffer.set(key, value);
|
|
88
|
-
|
|
88
|
+
this.createdKeys.add(key);
|
|
89
|
+
this.deleteBuffer.delete(key);
|
|
90
|
+
this.keyVersions.set(key, this.localVersion);
|
|
89
91
|
}
|
|
90
|
-
|
|
91
|
-
* Schedules a write (update) of a key-value pair.
|
|
92
|
-
* Overwrites any existing value in the buffer.
|
|
93
|
-
* @param key The key to write.
|
|
94
|
-
* @param value The value to store.
|
|
95
|
-
* @returns The transaction instance for chaining.
|
|
96
|
-
*/
|
|
97
|
-
write(key, value) {
|
|
98
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
92
|
+
_bufferWrite(key, value) {
|
|
99
93
|
this.localVersion++;
|
|
100
94
|
this.writeBuffer.set(key, value);
|
|
101
95
|
this.deleteBuffer.delete(key);
|
|
102
96
|
this.keyVersions.set(key, this.localVersion);
|
|
103
|
-
return this;
|
|
104
97
|
}
|
|
105
|
-
|
|
106
|
-
* Schedules a deletion of a key.
|
|
107
|
-
* @param key The key to delete.
|
|
108
|
-
* @returns The transaction instance for chaining.
|
|
109
|
-
*/
|
|
110
|
-
delete(key) {
|
|
111
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
98
|
+
_bufferDelete(key) {
|
|
112
99
|
this.localVersion++;
|
|
113
100
|
this.deleteBuffer.add(key);
|
|
114
101
|
this.writeBuffer.delete(key);
|
|
102
|
+
this.createdKeys.delete(key);
|
|
115
103
|
this.keyVersions.set(key, this.localVersion);
|
|
116
|
-
return this;
|
|
117
104
|
}
|
|
118
105
|
/**
|
|
119
106
|
* Rolls back the transaction.
|
|
120
107
|
* Clears all buffers and marks the transaction as finished.
|
|
121
|
-
* @returns The
|
|
108
|
+
* @returns The result object with success, created, updated, and deleted keys.
|
|
122
109
|
*/
|
|
123
110
|
rollback() {
|
|
111
|
+
const created = [];
|
|
112
|
+
const updated = [];
|
|
113
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
114
|
+
if (this.createdKeys.has(key)) {
|
|
115
|
+
created.push({ key, data });
|
|
116
|
+
} else {
|
|
117
|
+
updated.push({ key, data });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const deleted = [];
|
|
121
|
+
for (const key of this.deleteBuffer) {
|
|
122
|
+
const data = this.deletedValues.get(key);
|
|
123
|
+
if (data !== void 0) {
|
|
124
|
+
deleted.push({ key, data });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
124
127
|
this.writeBuffer.clear();
|
|
125
128
|
this.deleteBuffer.clear();
|
|
129
|
+
this.createdKeys.clear();
|
|
130
|
+
this.deletedValues.clear();
|
|
126
131
|
this.committed = true;
|
|
127
132
|
if (this.root !== this) {
|
|
128
133
|
this.root.activeTransactions.delete(this);
|
|
129
134
|
}
|
|
130
|
-
return
|
|
135
|
+
return { success: true, created, updated, deleted };
|
|
131
136
|
}
|
|
132
137
|
};
|
|
133
138
|
|
|
@@ -137,7 +142,39 @@ var SyncMVCCStrategy = class extends MVCCStrategy {
|
|
|
137
142
|
|
|
138
143
|
// src/core/sync/Transaction.ts
|
|
139
144
|
var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
145
|
+
create(key, value) {
|
|
146
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
147
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && this.read(key) !== null) {
|
|
148
|
+
throw new Error(`Key already exists: ${key}`);
|
|
149
|
+
}
|
|
150
|
+
this._bufferCreate(key, value);
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
write(key, value) {
|
|
154
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
155
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || this.read(key) === null)) {
|
|
156
|
+
throw new Error(`Key not found: ${key}`);
|
|
157
|
+
}
|
|
158
|
+
this._bufferWrite(key, value);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
delete(key) {
|
|
162
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
163
|
+
let valueToDelete = null;
|
|
164
|
+
if (this.writeBuffer.has(key)) {
|
|
165
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
166
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
167
|
+
valueToDelete = this.read(key);
|
|
168
|
+
}
|
|
169
|
+
if (valueToDelete === null) {
|
|
170
|
+
throw new Error(`Key not found: ${key}`);
|
|
171
|
+
}
|
|
172
|
+
this.deletedValues.set(key, valueToDelete);
|
|
173
|
+
this._bufferDelete(key);
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
140
176
|
createNested() {
|
|
177
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
141
178
|
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
142
179
|
const child = new _SyncMVCCTransaction(void 0, this, childVersion);
|
|
143
180
|
this.root.activeTransactions.add(child);
|
|
@@ -169,43 +206,79 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
169
206
|
}
|
|
170
207
|
}
|
|
171
208
|
commit() {
|
|
172
|
-
if (this.committed)
|
|
209
|
+
if (this.committed) {
|
|
210
|
+
return { success: false, error: "Transaction already committed", created: [], updated: [], deleted: [] };
|
|
211
|
+
}
|
|
212
|
+
const created = [];
|
|
213
|
+
const updated = [];
|
|
214
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
215
|
+
if (this.createdKeys.has(key)) {
|
|
216
|
+
created.push({ key, data });
|
|
217
|
+
} else {
|
|
218
|
+
updated.push({ key, data });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const deleted = [];
|
|
222
|
+
for (const key of this.deleteBuffer) {
|
|
223
|
+
const data = this.deletedValues.get(key);
|
|
224
|
+
if (data !== void 0) {
|
|
225
|
+
deleted.push({ key, data });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
173
228
|
if (this.parent) {
|
|
174
|
-
this.parent._merge(this);
|
|
229
|
+
const error = this.parent._merge(this);
|
|
230
|
+
if (error) {
|
|
231
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
232
|
+
}
|
|
175
233
|
this.committed = true;
|
|
176
234
|
} else {
|
|
177
235
|
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
178
|
-
this._merge(this);
|
|
236
|
+
const error = this._merge(this);
|
|
237
|
+
if (error) {
|
|
238
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
239
|
+
}
|
|
179
240
|
this.writeBuffer.clear();
|
|
180
241
|
this.deleteBuffer.clear();
|
|
242
|
+
this.createdKeys.clear();
|
|
243
|
+
this.deletedValues.clear();
|
|
181
244
|
this.keyVersions.clear();
|
|
182
245
|
this.localVersion = 0;
|
|
183
246
|
}
|
|
184
247
|
}
|
|
185
|
-
return
|
|
248
|
+
return { success: true, created, updated, deleted };
|
|
186
249
|
}
|
|
187
250
|
_merge(child) {
|
|
188
251
|
if (this.parent) {
|
|
189
252
|
for (const key of child.writeBuffer.keys()) {
|
|
190
253
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
191
254
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
192
|
-
|
|
255
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
193
256
|
}
|
|
194
257
|
}
|
|
195
258
|
for (const key of child.deleteBuffer) {
|
|
196
259
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
197
260
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
198
|
-
|
|
261
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
199
262
|
}
|
|
200
263
|
}
|
|
201
264
|
const newLocalVersion = this.localVersion + 1;
|
|
202
265
|
for (const key of child.writeBuffer.keys()) {
|
|
203
|
-
this.
|
|
266
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
267
|
+
this.deleteBuffer.delete(key);
|
|
204
268
|
this.keyVersions.set(key, newLocalVersion);
|
|
269
|
+
if (child.createdKeys.has(key)) {
|
|
270
|
+
this.createdKeys.add(key);
|
|
271
|
+
}
|
|
205
272
|
}
|
|
206
273
|
for (const key of child.deleteBuffer) {
|
|
207
|
-
this.
|
|
274
|
+
this.deleteBuffer.add(key);
|
|
275
|
+
this.writeBuffer.delete(key);
|
|
276
|
+
this.createdKeys.delete(key);
|
|
208
277
|
this.keyVersions.set(key, newLocalVersion);
|
|
278
|
+
const deletedValue = child.deletedValues.get(key);
|
|
279
|
+
if (deletedValue !== void 0) {
|
|
280
|
+
this.deletedValues.set(key, deletedValue);
|
|
281
|
+
}
|
|
209
282
|
}
|
|
210
283
|
this.localVersion = newLocalVersion;
|
|
211
284
|
this.root.activeTransactions.delete(child);
|
|
@@ -218,7 +291,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
218
291
|
if (versions && versions.length > 0) {
|
|
219
292
|
const lastVer = versions[versions.length - 1].version;
|
|
220
293
|
if (lastVer > child.snapshotVersion) {
|
|
221
|
-
|
|
294
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
|
|
222
295
|
}
|
|
223
296
|
}
|
|
224
297
|
}
|
|
@@ -231,6 +304,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
231
304
|
this.version = newVersion;
|
|
232
305
|
this._cleanupDeletedCache();
|
|
233
306
|
}
|
|
307
|
+
return null;
|
|
234
308
|
}
|
|
235
309
|
// --- Internal IO Helpers (Root Only) ---
|
|
236
310
|
_diskWrite(key, value, version) {
|
|
@@ -586,7 +660,39 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
586
660
|
this.lock.writeUnlock(lockId);
|
|
587
661
|
});
|
|
588
662
|
}
|
|
663
|
+
async create(key, value) {
|
|
664
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
665
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && await this.read(key) !== null) {
|
|
666
|
+
throw new Error(`Key already exists: ${key}`);
|
|
667
|
+
}
|
|
668
|
+
this._bufferCreate(key, value);
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
671
|
+
async write(key, value) {
|
|
672
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
673
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || await this.read(key) === null)) {
|
|
674
|
+
throw new Error(`Key not found: ${key}`);
|
|
675
|
+
}
|
|
676
|
+
this._bufferWrite(key, value);
|
|
677
|
+
return this;
|
|
678
|
+
}
|
|
679
|
+
async delete(key) {
|
|
680
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
681
|
+
let valueToDelete = null;
|
|
682
|
+
if (this.writeBuffer.has(key)) {
|
|
683
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
684
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
685
|
+
valueToDelete = await this.read(key);
|
|
686
|
+
}
|
|
687
|
+
if (valueToDelete === null) {
|
|
688
|
+
throw new Error(`Key not found: ${key}`);
|
|
689
|
+
}
|
|
690
|
+
this.deletedValues.set(key, valueToDelete);
|
|
691
|
+
this._bufferDelete(key);
|
|
692
|
+
return this;
|
|
693
|
+
}
|
|
589
694
|
createNested() {
|
|
695
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
590
696
|
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
591
697
|
const child = new _AsyncMVCCTransaction(void 0, this, childVersion);
|
|
592
698
|
this.root.activeTransactions.add(child);
|
|
@@ -619,20 +725,46 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
619
725
|
}
|
|
620
726
|
async commit() {
|
|
621
727
|
return this.writeLock(async () => {
|
|
622
|
-
if (this.committed)
|
|
728
|
+
if (this.committed) {
|
|
729
|
+
return { success: false, error: "Transaction already committed", created: [], updated: [], deleted: [] };
|
|
730
|
+
}
|
|
731
|
+
const created = [];
|
|
732
|
+
const updated = [];
|
|
733
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
734
|
+
if (this.createdKeys.has(key)) {
|
|
735
|
+
created.push({ key, data });
|
|
736
|
+
} else {
|
|
737
|
+
updated.push({ key, data });
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const deleted = [];
|
|
741
|
+
for (const key of this.deleteBuffer) {
|
|
742
|
+
const data = this.deletedValues.get(key);
|
|
743
|
+
if (data !== void 0) {
|
|
744
|
+
deleted.push({ key, data });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
623
747
|
if (this.parent) {
|
|
624
|
-
await this.parent._merge(this);
|
|
748
|
+
const error = await this.parent._merge(this);
|
|
749
|
+
if (error) {
|
|
750
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
751
|
+
}
|
|
625
752
|
this.committed = true;
|
|
626
753
|
} else {
|
|
627
754
|
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
628
|
-
await this._merge(this);
|
|
755
|
+
const error = await this._merge(this);
|
|
756
|
+
if (error) {
|
|
757
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
758
|
+
}
|
|
629
759
|
this.writeBuffer.clear();
|
|
630
760
|
this.deleteBuffer.clear();
|
|
761
|
+
this.createdKeys.clear();
|
|
762
|
+
this.deletedValues.clear();
|
|
631
763
|
this.keyVersions.clear();
|
|
632
764
|
this.localVersion = 0;
|
|
633
765
|
}
|
|
634
766
|
}
|
|
635
|
-
return
|
|
767
|
+
return { success: true, created, updated, deleted };
|
|
636
768
|
});
|
|
637
769
|
}
|
|
638
770
|
async _merge(child) {
|
|
@@ -641,23 +773,33 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
641
773
|
for (const key of child.writeBuffer.keys()) {
|
|
642
774
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
643
775
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
644
|
-
|
|
776
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
645
777
|
}
|
|
646
778
|
}
|
|
647
779
|
for (const key of child.deleteBuffer) {
|
|
648
780
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
649
781
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
650
|
-
|
|
782
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
651
783
|
}
|
|
652
784
|
}
|
|
653
785
|
const newLocalVersion = this.localVersion + 1;
|
|
654
786
|
for (const key of child.writeBuffer.keys()) {
|
|
655
|
-
this.
|
|
787
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
788
|
+
this.deleteBuffer.delete(key);
|
|
656
789
|
this.keyVersions.set(key, newLocalVersion);
|
|
790
|
+
if (child.createdKeys.has(key)) {
|
|
791
|
+
this.createdKeys.add(key);
|
|
792
|
+
}
|
|
657
793
|
}
|
|
658
794
|
for (const key of child.deleteBuffer) {
|
|
659
|
-
this.
|
|
795
|
+
this.deleteBuffer.add(key);
|
|
796
|
+
this.writeBuffer.delete(key);
|
|
797
|
+
this.createdKeys.delete(key);
|
|
660
798
|
this.keyVersions.set(key, newLocalVersion);
|
|
799
|
+
const deletedValue = child.deletedValues.get(key);
|
|
800
|
+
if (deletedValue !== void 0) {
|
|
801
|
+
this.deletedValues.set(key, deletedValue);
|
|
802
|
+
}
|
|
661
803
|
}
|
|
662
804
|
this.localVersion = newLocalVersion;
|
|
663
805
|
this.root.activeTransactions.delete(child);
|
|
@@ -670,7 +812,7 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
670
812
|
if (versions && versions.length > 0) {
|
|
671
813
|
const lastVer = versions[versions.length - 1].version;
|
|
672
814
|
if (lastVer > child.snapshotVersion) {
|
|
673
|
-
|
|
815
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
|
|
674
816
|
}
|
|
675
817
|
}
|
|
676
818
|
}
|
|
@@ -683,6 +825,7 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
683
825
|
this.version = newVersion;
|
|
684
826
|
this._cleanupDeletedCache();
|
|
685
827
|
}
|
|
828
|
+
return null;
|
|
686
829
|
});
|
|
687
830
|
}
|
|
688
831
|
// --- Internal IO Helpers (Root Only) ---
|
package/dist/esm/index.mjs
CHANGED
|
@@ -9,6 +9,10 @@ var MVCCTransaction = class {
|
|
|
9
9
|
snapshotLocalVersion;
|
|
10
10
|
writeBuffer;
|
|
11
11
|
deleteBuffer;
|
|
12
|
+
createdKeys;
|
|
13
|
+
// create()로 생성된 키 추적
|
|
14
|
+
deletedValues;
|
|
15
|
+
// delete 시 삭제 전 값 저장
|
|
12
16
|
// Nested Transaction Properties
|
|
13
17
|
parent;
|
|
14
18
|
localVersion;
|
|
@@ -26,6 +30,8 @@ var MVCCTransaction = class {
|
|
|
26
30
|
this.snapshotVersion = snapshotVersion ?? 0;
|
|
27
31
|
this.writeBuffer = /* @__PURE__ */ new Map();
|
|
28
32
|
this.deleteBuffer = /* @__PURE__ */ new Set();
|
|
33
|
+
this.createdKeys = /* @__PURE__ */ new Set();
|
|
34
|
+
this.deletedValues = /* @__PURE__ */ new Map();
|
|
29
35
|
this.committed = false;
|
|
30
36
|
this.parent = parent;
|
|
31
37
|
this.keyVersions = /* @__PURE__ */ new Map();
|
|
@@ -46,59 +52,58 @@ var MVCCTransaction = class {
|
|
|
46
52
|
isRoot() {
|
|
47
53
|
return !this.parent;
|
|
48
54
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @param key The key to create.
|
|
53
|
-
* @param value The value to store.
|
|
54
|
-
* @returns The transaction instance for chaining.
|
|
55
|
-
*/
|
|
56
|
-
create(key, value) {
|
|
57
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
55
|
+
// --- Internal buffer manipulation helpers ---
|
|
56
|
+
_bufferCreate(key, value) {
|
|
57
|
+
this.localVersion++;
|
|
58
58
|
this.writeBuffer.set(key, value);
|
|
59
|
-
|
|
59
|
+
this.createdKeys.add(key);
|
|
60
|
+
this.deleteBuffer.delete(key);
|
|
61
|
+
this.keyVersions.set(key, this.localVersion);
|
|
60
62
|
}
|
|
61
|
-
|
|
62
|
-
* Schedules a write (update) of a key-value pair.
|
|
63
|
-
* Overwrites any existing value in the buffer.
|
|
64
|
-
* @param key The key to write.
|
|
65
|
-
* @param value The value to store.
|
|
66
|
-
* @returns The transaction instance for chaining.
|
|
67
|
-
*/
|
|
68
|
-
write(key, value) {
|
|
69
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
63
|
+
_bufferWrite(key, value) {
|
|
70
64
|
this.localVersion++;
|
|
71
65
|
this.writeBuffer.set(key, value);
|
|
72
66
|
this.deleteBuffer.delete(key);
|
|
73
67
|
this.keyVersions.set(key, this.localVersion);
|
|
74
|
-
return this;
|
|
75
68
|
}
|
|
76
|
-
|
|
77
|
-
* Schedules a deletion of a key.
|
|
78
|
-
* @param key The key to delete.
|
|
79
|
-
* @returns The transaction instance for chaining.
|
|
80
|
-
*/
|
|
81
|
-
delete(key) {
|
|
82
|
-
if (this.committed) throw new Error("Transaction already committed");
|
|
69
|
+
_bufferDelete(key) {
|
|
83
70
|
this.localVersion++;
|
|
84
71
|
this.deleteBuffer.add(key);
|
|
85
72
|
this.writeBuffer.delete(key);
|
|
73
|
+
this.createdKeys.delete(key);
|
|
86
74
|
this.keyVersions.set(key, this.localVersion);
|
|
87
|
-
return this;
|
|
88
75
|
}
|
|
89
76
|
/**
|
|
90
77
|
* Rolls back the transaction.
|
|
91
78
|
* Clears all buffers and marks the transaction as finished.
|
|
92
|
-
* @returns The
|
|
79
|
+
* @returns The result object with success, created, updated, and deleted keys.
|
|
93
80
|
*/
|
|
94
81
|
rollback() {
|
|
82
|
+
const created = [];
|
|
83
|
+
const updated = [];
|
|
84
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
85
|
+
if (this.createdKeys.has(key)) {
|
|
86
|
+
created.push({ key, data });
|
|
87
|
+
} else {
|
|
88
|
+
updated.push({ key, data });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const deleted = [];
|
|
92
|
+
for (const key of this.deleteBuffer) {
|
|
93
|
+
const data = this.deletedValues.get(key);
|
|
94
|
+
if (data !== void 0) {
|
|
95
|
+
deleted.push({ key, data });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
95
98
|
this.writeBuffer.clear();
|
|
96
99
|
this.deleteBuffer.clear();
|
|
100
|
+
this.createdKeys.clear();
|
|
101
|
+
this.deletedValues.clear();
|
|
97
102
|
this.committed = true;
|
|
98
103
|
if (this.root !== this) {
|
|
99
104
|
this.root.activeTransactions.delete(this);
|
|
100
105
|
}
|
|
101
|
-
return
|
|
106
|
+
return { success: true, created, updated, deleted };
|
|
102
107
|
}
|
|
103
108
|
};
|
|
104
109
|
|
|
@@ -108,7 +113,39 @@ var SyncMVCCStrategy = class extends MVCCStrategy {
|
|
|
108
113
|
|
|
109
114
|
// src/core/sync/Transaction.ts
|
|
110
115
|
var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
116
|
+
create(key, value) {
|
|
117
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
118
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && this.read(key) !== null) {
|
|
119
|
+
throw new Error(`Key already exists: ${key}`);
|
|
120
|
+
}
|
|
121
|
+
this._bufferCreate(key, value);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
write(key, value) {
|
|
125
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
126
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || this.read(key) === null)) {
|
|
127
|
+
throw new Error(`Key not found: ${key}`);
|
|
128
|
+
}
|
|
129
|
+
this._bufferWrite(key, value);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
delete(key) {
|
|
133
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
134
|
+
let valueToDelete = null;
|
|
135
|
+
if (this.writeBuffer.has(key)) {
|
|
136
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
137
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
138
|
+
valueToDelete = this.read(key);
|
|
139
|
+
}
|
|
140
|
+
if (valueToDelete === null) {
|
|
141
|
+
throw new Error(`Key not found: ${key}`);
|
|
142
|
+
}
|
|
143
|
+
this.deletedValues.set(key, valueToDelete);
|
|
144
|
+
this._bufferDelete(key);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
111
147
|
createNested() {
|
|
148
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
112
149
|
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
113
150
|
const child = new _SyncMVCCTransaction(void 0, this, childVersion);
|
|
114
151
|
this.root.activeTransactions.add(child);
|
|
@@ -140,43 +177,79 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
140
177
|
}
|
|
141
178
|
}
|
|
142
179
|
commit() {
|
|
143
|
-
if (this.committed)
|
|
180
|
+
if (this.committed) {
|
|
181
|
+
return { success: false, error: "Transaction already committed", created: [], updated: [], deleted: [] };
|
|
182
|
+
}
|
|
183
|
+
const created = [];
|
|
184
|
+
const updated = [];
|
|
185
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
186
|
+
if (this.createdKeys.has(key)) {
|
|
187
|
+
created.push({ key, data });
|
|
188
|
+
} else {
|
|
189
|
+
updated.push({ key, data });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const deleted = [];
|
|
193
|
+
for (const key of this.deleteBuffer) {
|
|
194
|
+
const data = this.deletedValues.get(key);
|
|
195
|
+
if (data !== void 0) {
|
|
196
|
+
deleted.push({ key, data });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
144
199
|
if (this.parent) {
|
|
145
|
-
this.parent._merge(this);
|
|
200
|
+
const error = this.parent._merge(this);
|
|
201
|
+
if (error) {
|
|
202
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
203
|
+
}
|
|
146
204
|
this.committed = true;
|
|
147
205
|
} else {
|
|
148
206
|
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
149
|
-
this._merge(this);
|
|
207
|
+
const error = this._merge(this);
|
|
208
|
+
if (error) {
|
|
209
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
210
|
+
}
|
|
150
211
|
this.writeBuffer.clear();
|
|
151
212
|
this.deleteBuffer.clear();
|
|
213
|
+
this.createdKeys.clear();
|
|
214
|
+
this.deletedValues.clear();
|
|
152
215
|
this.keyVersions.clear();
|
|
153
216
|
this.localVersion = 0;
|
|
154
217
|
}
|
|
155
218
|
}
|
|
156
|
-
return
|
|
219
|
+
return { success: true, created, updated, deleted };
|
|
157
220
|
}
|
|
158
221
|
_merge(child) {
|
|
159
222
|
if (this.parent) {
|
|
160
223
|
for (const key of child.writeBuffer.keys()) {
|
|
161
224
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
162
225
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
163
|
-
|
|
226
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
164
227
|
}
|
|
165
228
|
}
|
|
166
229
|
for (const key of child.deleteBuffer) {
|
|
167
230
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
168
231
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
169
|
-
|
|
232
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
170
233
|
}
|
|
171
234
|
}
|
|
172
235
|
const newLocalVersion = this.localVersion + 1;
|
|
173
236
|
for (const key of child.writeBuffer.keys()) {
|
|
174
|
-
this.
|
|
237
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
238
|
+
this.deleteBuffer.delete(key);
|
|
175
239
|
this.keyVersions.set(key, newLocalVersion);
|
|
240
|
+
if (child.createdKeys.has(key)) {
|
|
241
|
+
this.createdKeys.add(key);
|
|
242
|
+
}
|
|
176
243
|
}
|
|
177
244
|
for (const key of child.deleteBuffer) {
|
|
178
|
-
this.
|
|
245
|
+
this.deleteBuffer.add(key);
|
|
246
|
+
this.writeBuffer.delete(key);
|
|
247
|
+
this.createdKeys.delete(key);
|
|
179
248
|
this.keyVersions.set(key, newLocalVersion);
|
|
249
|
+
const deletedValue = child.deletedValues.get(key);
|
|
250
|
+
if (deletedValue !== void 0) {
|
|
251
|
+
this.deletedValues.set(key, deletedValue);
|
|
252
|
+
}
|
|
180
253
|
}
|
|
181
254
|
this.localVersion = newLocalVersion;
|
|
182
255
|
this.root.activeTransactions.delete(child);
|
|
@@ -189,7 +262,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
189
262
|
if (versions && versions.length > 0) {
|
|
190
263
|
const lastVer = versions[versions.length - 1].version;
|
|
191
264
|
if (lastVer > child.snapshotVersion) {
|
|
192
|
-
|
|
265
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
|
|
193
266
|
}
|
|
194
267
|
}
|
|
195
268
|
}
|
|
@@ -202,6 +275,7 @@ var SyncMVCCTransaction = class _SyncMVCCTransaction extends MVCCTransaction {
|
|
|
202
275
|
this.version = newVersion;
|
|
203
276
|
this._cleanupDeletedCache();
|
|
204
277
|
}
|
|
278
|
+
return null;
|
|
205
279
|
}
|
|
206
280
|
// --- Internal IO Helpers (Root Only) ---
|
|
207
281
|
_diskWrite(key, value, version) {
|
|
@@ -557,7 +631,39 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
557
631
|
this.lock.writeUnlock(lockId);
|
|
558
632
|
});
|
|
559
633
|
}
|
|
634
|
+
async create(key, value) {
|
|
635
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
636
|
+
if (this.writeBuffer.has(key) || !this.deleteBuffer.has(key) && await this.read(key) !== null) {
|
|
637
|
+
throw new Error(`Key already exists: ${key}`);
|
|
638
|
+
}
|
|
639
|
+
this._bufferCreate(key, value);
|
|
640
|
+
return this;
|
|
641
|
+
}
|
|
642
|
+
async write(key, value) {
|
|
643
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
644
|
+
if (!this.writeBuffer.has(key) && (this.deleteBuffer.has(key) || await this.read(key) === null)) {
|
|
645
|
+
throw new Error(`Key not found: ${key}`);
|
|
646
|
+
}
|
|
647
|
+
this._bufferWrite(key, value);
|
|
648
|
+
return this;
|
|
649
|
+
}
|
|
650
|
+
async delete(key) {
|
|
651
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
652
|
+
let valueToDelete = null;
|
|
653
|
+
if (this.writeBuffer.has(key)) {
|
|
654
|
+
valueToDelete = this.writeBuffer.get(key);
|
|
655
|
+
} else if (!this.deleteBuffer.has(key)) {
|
|
656
|
+
valueToDelete = await this.read(key);
|
|
657
|
+
}
|
|
658
|
+
if (valueToDelete === null) {
|
|
659
|
+
throw new Error(`Key not found: ${key}`);
|
|
660
|
+
}
|
|
661
|
+
this.deletedValues.set(key, valueToDelete);
|
|
662
|
+
this._bufferDelete(key);
|
|
663
|
+
return this;
|
|
664
|
+
}
|
|
560
665
|
createNested() {
|
|
666
|
+
if (this.committed) throw new Error("Transaction already committed");
|
|
561
667
|
const childVersion = this.isRoot() ? this.version : this.snapshotVersion;
|
|
562
668
|
const child = new _AsyncMVCCTransaction(void 0, this, childVersion);
|
|
563
669
|
this.root.activeTransactions.add(child);
|
|
@@ -590,20 +696,46 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
590
696
|
}
|
|
591
697
|
async commit() {
|
|
592
698
|
return this.writeLock(async () => {
|
|
593
|
-
if (this.committed)
|
|
699
|
+
if (this.committed) {
|
|
700
|
+
return { success: false, error: "Transaction already committed", created: [], updated: [], deleted: [] };
|
|
701
|
+
}
|
|
702
|
+
const created = [];
|
|
703
|
+
const updated = [];
|
|
704
|
+
for (const [key, data] of this.writeBuffer.entries()) {
|
|
705
|
+
if (this.createdKeys.has(key)) {
|
|
706
|
+
created.push({ key, data });
|
|
707
|
+
} else {
|
|
708
|
+
updated.push({ key, data });
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const deleted = [];
|
|
712
|
+
for (const key of this.deleteBuffer) {
|
|
713
|
+
const data = this.deletedValues.get(key);
|
|
714
|
+
if (data !== void 0) {
|
|
715
|
+
deleted.push({ key, data });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
594
718
|
if (this.parent) {
|
|
595
|
-
await this.parent._merge(this);
|
|
719
|
+
const error = await this.parent._merge(this);
|
|
720
|
+
if (error) {
|
|
721
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
722
|
+
}
|
|
596
723
|
this.committed = true;
|
|
597
724
|
} else {
|
|
598
725
|
if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
|
|
599
|
-
await this._merge(this);
|
|
726
|
+
const error = await this._merge(this);
|
|
727
|
+
if (error) {
|
|
728
|
+
return { success: false, error, created: [], updated: [], deleted: [] };
|
|
729
|
+
}
|
|
600
730
|
this.writeBuffer.clear();
|
|
601
731
|
this.deleteBuffer.clear();
|
|
732
|
+
this.createdKeys.clear();
|
|
733
|
+
this.deletedValues.clear();
|
|
602
734
|
this.keyVersions.clear();
|
|
603
735
|
this.localVersion = 0;
|
|
604
736
|
}
|
|
605
737
|
}
|
|
606
|
-
return
|
|
738
|
+
return { success: true, created, updated, deleted };
|
|
607
739
|
});
|
|
608
740
|
}
|
|
609
741
|
async _merge(child) {
|
|
@@ -612,23 +744,33 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
612
744
|
for (const key of child.writeBuffer.keys()) {
|
|
613
745
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
614
746
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
615
|
-
|
|
747
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
616
748
|
}
|
|
617
749
|
}
|
|
618
750
|
for (const key of child.deleteBuffer) {
|
|
619
751
|
const lastModLocalVer = this.keyVersions.get(key);
|
|
620
752
|
if (lastModLocalVer !== void 0 && lastModLocalVer > child.snapshotLocalVersion) {
|
|
621
|
-
|
|
753
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`;
|
|
622
754
|
}
|
|
623
755
|
}
|
|
624
756
|
const newLocalVersion = this.localVersion + 1;
|
|
625
757
|
for (const key of child.writeBuffer.keys()) {
|
|
626
|
-
this.
|
|
758
|
+
this.writeBuffer.set(key, child.writeBuffer.get(key));
|
|
759
|
+
this.deleteBuffer.delete(key);
|
|
627
760
|
this.keyVersions.set(key, newLocalVersion);
|
|
761
|
+
if (child.createdKeys.has(key)) {
|
|
762
|
+
this.createdKeys.add(key);
|
|
763
|
+
}
|
|
628
764
|
}
|
|
629
765
|
for (const key of child.deleteBuffer) {
|
|
630
|
-
this.
|
|
766
|
+
this.deleteBuffer.add(key);
|
|
767
|
+
this.writeBuffer.delete(key);
|
|
768
|
+
this.createdKeys.delete(key);
|
|
631
769
|
this.keyVersions.set(key, newLocalVersion);
|
|
770
|
+
const deletedValue = child.deletedValues.get(key);
|
|
771
|
+
if (deletedValue !== void 0) {
|
|
772
|
+
this.deletedValues.set(key, deletedValue);
|
|
773
|
+
}
|
|
632
774
|
}
|
|
633
775
|
this.localVersion = newLocalVersion;
|
|
634
776
|
this.root.activeTransactions.delete(child);
|
|
@@ -641,7 +783,7 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
641
783
|
if (versions && versions.length > 0) {
|
|
642
784
|
const lastVer = versions[versions.length - 1].version;
|
|
643
785
|
if (lastVer > child.snapshotVersion) {
|
|
644
|
-
|
|
786
|
+
return `Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`;
|
|
645
787
|
}
|
|
646
788
|
}
|
|
647
789
|
}
|
|
@@ -654,6 +796,7 @@ var AsyncMVCCTransaction = class _AsyncMVCCTransaction extends MVCCTransaction {
|
|
|
654
796
|
this.version = newVersion;
|
|
655
797
|
this._cleanupDeletedCache();
|
|
656
798
|
}
|
|
799
|
+
return null;
|
|
657
800
|
});
|
|
658
801
|
}
|
|
659
802
|
// --- Internal IO Helpers (Root Only) ---
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { AsyncMVCCStrategy } from './Strategy';
|
|
2
|
+
import type { TransactionResult } from '../../types';
|
|
2
3
|
import { MVCCTransaction } from '../base';
|
|
3
4
|
export declare class AsyncMVCCTransaction<S extends AsyncMVCCStrategy<K, T>, K, T> extends MVCCTransaction<S, K, T> {
|
|
4
5
|
private lock;
|
|
5
6
|
private writeLock;
|
|
7
|
+
create(key: K, value: T): Promise<this>;
|
|
8
|
+
write(key: K, value: T): Promise<this>;
|
|
9
|
+
delete(key: K): Promise<this>;
|
|
6
10
|
createNested(): this;
|
|
7
11
|
read(key: K): Promise<T | null>;
|
|
8
12
|
_readSnapshot(key: K, snapshotVersion: number, snapshotLocalVersion?: number): Promise<T | null>;
|
|
9
|
-
commit(): Promise<
|
|
10
|
-
_merge(child: MVCCTransaction<S, K, T>): Promise<
|
|
13
|
+
commit(): Promise<TransactionResult<K, T>>;
|
|
14
|
+
_merge(child: MVCCTransaction<S, K, T>): Promise<string | null>;
|
|
11
15
|
_diskWrite(key: K, value: T, version: number): Promise<void>;
|
|
12
16
|
_diskRead(key: K, snapshotVersion: number): Promise<T | null>;
|
|
13
17
|
_diskDelete(key: K, snapshotVersion: number): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Deferred } from '../../types';
|
|
1
|
+
import type { Deferred, TransactionResult } from '../../types';
|
|
2
2
|
import type { MVCCStrategy } from './Strategy';
|
|
3
3
|
/**
|
|
4
4
|
* MVCC Transaction abstract class.
|
|
@@ -14,6 +14,8 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
|
|
|
14
14
|
readonly snapshotLocalVersion: number;
|
|
15
15
|
readonly writeBuffer: Map<K, T>;
|
|
16
16
|
readonly deleteBuffer: Set<K>;
|
|
17
|
+
readonly createdKeys: Set<K>;
|
|
18
|
+
readonly deletedValues: Map<K, T>;
|
|
17
19
|
readonly parent?: MVCCTransaction<S, K, T>;
|
|
18
20
|
localVersion: number;
|
|
19
21
|
readonly keyVersions: Map<K, number>;
|
|
@@ -33,32 +35,36 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
|
|
|
33
35
|
isRoot(): boolean;
|
|
34
36
|
/**
|
|
35
37
|
* Schedules a creation (insert) of a key-value pair.
|
|
36
|
-
* Throws if the
|
|
38
|
+
* Throws if the key already exists.
|
|
37
39
|
* @param key The key to create.
|
|
38
40
|
* @param value The value to store.
|
|
39
41
|
* @returns The transaction instance for chaining.
|
|
40
42
|
*/
|
|
41
|
-
create(key: K, value: T): this
|
|
43
|
+
abstract create(key: K, value: T): Deferred<this>;
|
|
42
44
|
/**
|
|
43
45
|
* Schedules a write (update) of a key-value pair.
|
|
44
|
-
*
|
|
46
|
+
* Throws if the key does not exist.
|
|
45
47
|
* @param key The key to write.
|
|
46
48
|
* @param value The value to store.
|
|
47
49
|
* @returns The transaction instance for chaining.
|
|
48
50
|
*/
|
|
49
|
-
write(key: K, value: T): this
|
|
51
|
+
abstract write(key: K, value: T): Deferred<this>;
|
|
50
52
|
/**
|
|
51
53
|
* Schedules a deletion of a key.
|
|
54
|
+
* Throws if the key does not exist.
|
|
52
55
|
* @param key The key to delete.
|
|
53
56
|
* @returns The transaction instance for chaining.
|
|
54
57
|
*/
|
|
55
|
-
delete(key: K): this
|
|
58
|
+
abstract delete(key: K): Deferred<this>;
|
|
59
|
+
protected _bufferCreate(key: K, value: T): void;
|
|
60
|
+
protected _bufferWrite(key: K, value: T): void;
|
|
61
|
+
protected _bufferDelete(key: K): void;
|
|
56
62
|
/**
|
|
57
63
|
* Rolls back the transaction.
|
|
58
64
|
* Clears all buffers and marks the transaction as finished.
|
|
59
|
-
* @returns The
|
|
65
|
+
* @returns The result object with success, created, updated, and deleted keys.
|
|
60
66
|
*/
|
|
61
|
-
rollback():
|
|
67
|
+
rollback(): TransactionResult<K, T>;
|
|
62
68
|
/**
|
|
63
69
|
* Reads a value respecting the transaction's snapshot and local changes.
|
|
64
70
|
* @param key The key to read.
|
|
@@ -69,9 +75,9 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
|
|
|
69
75
|
* Commits the transaction.
|
|
70
76
|
* If root, persists to storage.
|
|
71
77
|
* If nested, merges to parent.
|
|
72
|
-
* @returns The
|
|
78
|
+
* @returns The result object with success, created, and obsolete keys.
|
|
73
79
|
*/
|
|
74
|
-
abstract commit(): Deferred<
|
|
80
|
+
abstract commit(): Deferred<TransactionResult<K, T>>;
|
|
75
81
|
/**
|
|
76
82
|
* Creates a nested transaction (child) from this transaction.
|
|
77
83
|
* @returns A new nested transaction instance.
|
|
@@ -80,8 +86,9 @@ export declare abstract class MVCCTransaction<S extends MVCCStrategy<K, T>, K, T
|
|
|
80
86
|
/**
|
|
81
87
|
* Merges a child transaction's changes into this transaction.
|
|
82
88
|
* @param child The committed child transaction.
|
|
89
|
+
* @returns Error message if conflict, null if success.
|
|
83
90
|
*/
|
|
84
|
-
abstract _merge(child: MVCCTransaction<S, K, T>): Deferred<
|
|
91
|
+
abstract _merge(child: MVCCTransaction<S, K, T>): Deferred<string | null>;
|
|
85
92
|
/**
|
|
86
93
|
* Reads a value at a specific snapshot version.
|
|
87
94
|
* Used by child transactions to read from parent respecting the child's snapshot.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { SyncMVCCStrategy } from './Strategy';
|
|
2
|
+
import type { TransactionResult } from '../../types';
|
|
2
3
|
import { MVCCTransaction } from '../base';
|
|
3
4
|
export declare class SyncMVCCTransaction<S extends SyncMVCCStrategy<K, T>, K, T> extends MVCCTransaction<S, K, T> {
|
|
5
|
+
create(key: K, value: T): this;
|
|
6
|
+
write(key: K, value: T): this;
|
|
7
|
+
delete(key: K): this;
|
|
4
8
|
createNested(): this;
|
|
5
9
|
read(key: K): T | null;
|
|
6
10
|
_readSnapshot(key: K, snapshotVersion: number, snapshotLocalVersion?: number): T | null;
|
|
7
|
-
commit():
|
|
8
|
-
_merge(child: MVCCTransaction<S, K, T>):
|
|
11
|
+
commit(): TransactionResult<K, T>;
|
|
12
|
+
_merge(child: MVCCTransaction<S, K, T>): string | null;
|
|
9
13
|
_diskWrite(key: K, value: T, version: number): void;
|
|
10
14
|
_diskRead(key: K, snapshotVersion: number): T | null;
|
|
11
15
|
_diskDelete(key: K, snapshotVersion: number): void;
|
|
@@ -3,3 +3,14 @@ export type DeleteEntry<T> = {
|
|
|
3
3
|
value: T;
|
|
4
4
|
deletedAtVersion: number;
|
|
5
5
|
};
|
|
6
|
+
export type TransactionEntry<K, T> = {
|
|
7
|
+
key: K;
|
|
8
|
+
data: T;
|
|
9
|
+
};
|
|
10
|
+
export type TransactionResult<K, T> = {
|
|
11
|
+
success: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
created: TransactionEntry<K, T>[];
|
|
14
|
+
updated: TransactionEntry<K, T>[];
|
|
15
|
+
deleted: TransactionEntry<K, T>[];
|
|
16
|
+
};
|