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 CHANGED
@@ -5,149 +5,200 @@
5
5
 
6
6
  Multiversion Concurrency Control (MVCC) API for TypeScript.
7
7
 
8
- This library provides a robust framework for implementing Snapshot Isolation (SI) using MVCC. It supports both synchronous and asynchronous operations and features a flexible nested transaction system.
8
+ It implements Snapshot Isolation and supports synchronous/asynchronous operations and flexible nested transactions.
9
9
 
10
- ## Features
10
+ ## Key Features
11
11
 
12
- - **MVCC (Multiversion Concurrency Control)**: Provides Snapshot Isolation, allowing readers to not block writers and vice versa.
13
- - **Strict Committed-Only Visibility**: Nested transactions only see data that has been globally committed. Uncommitted changes in parent buffers are isolated from child transactions.
14
- - **Reusable Root Transaction**: The Root transaction serves as a persistent gateway to storage and can be committed multiple times without being closed.
15
- - **Unified Transaction Architecture**: Root and Nested transactions share the same API, simplifying complex workflows.
16
- - **Indefinite Nesting**: Create child transactions from any existing transaction with proper conflict detection.
17
- - **Storage Agnostic**: Implement your own `Strategy` (e.g., File System, In-Memory, Key-Value Store) via the Strategy pattern.
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
- ## Installation
20
+ ## Why `mvcc-api`?
20
21
 
21
- ### Node.js
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
- ```bash
24
- npm install mvcc-api
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
- ### ES Module (via CDN)
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
- ```javascript
30
- import {
31
- AsyncMVCCTransaction,
32
- AsyncMVCCStrategy
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 a Strategy
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 AsyncFileStrategy extends AsyncMVCCStrategy<string, string> {
47
- async read(key: string): Promise<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): Promise<void> {
51
- await fs.promises.writeFile(key, value, 'utf-8')
57
+ async write(key: string, value: string) {
58
+ await fs.promises.writeFile(key, value)
52
59
  }
53
- async delete(key: string): Promise<void> {
60
+ async delete(key: string) {
54
61
  await fs.promises.unlink(key)
55
62
  }
56
- async exists(key: string): Promise<boolean> {
63
+ async exists(key: string) {
57
64
  return fs.existsSync(key)
58
65
  }
59
66
  }
60
67
  ```
61
68
 
62
- ### 2. Run Transactions
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
- async function main() {
71
- const strategy = new AsyncFileStrategy()
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
- // Start a Nested Transaction for isolated work
76
- const tx = root.createNested()
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
- try {
79
- tx.write('data.json', JSON.stringify({ status: 'active' }))
80
-
81
- // Child sees its own buffer, but parent's uncommitted buffer is hidden
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
- ## Architecture & Visibility
86
+ await root.commit() // Persist to storage
87
+ ```
96
88
 
97
- The following diagram illustrates how visibility and data flow work in `mvcc-api`. Child transactions create an immutable snapshot of the **globally committed state** at the moment of their creation.
89
+ ## Visibility Rules
98
90
 
99
91
  ```mermaid
100
92
  sequenceDiagram
101
- participant S as Storage (Strategy)
102
- participant R as Root Transaction
103
- participant N as Nested Transaction
104
-
105
- Note over S, R: Global Version 1
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, 240, 240)
111
- Note over N: Create Nested
112
- N->>S: read('k1') at v1
113
- Note right of N: Sees: null (v1 not committed yet)
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
- R->>S: commit()
117
- Note over S, R: Global Version 2
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
- rect rgb(200, 255, 200)
120
- Note over N2: Create New Nested
121
- N2->>S: read('k1') at v2
122
- Note right of N2: Sees: 'v1'
123
- end
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
- ### Visibility Rules
127
- 1. **Self-Visibility**: A transaction always sees its own uncommitted changes.
128
- 2. **Strict Isolation**: A child transaction **cannot** see its parent's uncommitted buffer. It only sees data committed to the storage at the time of the child's creation.
129
- 3. **Snapshot Integrity**: Once a child is created, it will never see any subsequent commits made by other transactions (including its parent) until it is closed.
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>` (Sync/Async)
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
- ## License
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
@@ -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
- * Schedules a creation (insert) of a key-value pair.
80
- * Throws if the transaction is already committed.
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
- return this;
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 transaction instance.
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 this;
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) throw new Error("Transaction already 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 this;
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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.write(key, child.writeBuffer.get(key));
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.delete(key);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`);
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) throw new Error("Transaction already 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 this;
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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.write(key, child.writeBuffer.get(key));
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.delete(key);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`);
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) ---
@@ -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
- * Schedules a creation (insert) of a key-value pair.
51
- * Throws if the transaction is already committed.
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
- return this;
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 transaction instance.
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 this;
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) throw new Error("Transaction already 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 this;
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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.write(key, child.writeBuffer.get(key));
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.delete(key);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`);
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) throw new Error("Transaction already 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 this;
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (Local v${lastModLocalVer})`);
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.write(key, child.writeBuffer.get(key));
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.delete(key);
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
- throw new Error(`Commit conflict: Key '${key}' was modified by a newer transaction (v${lastVer})`);
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<this>;
10
- _merge(child: MVCCTransaction<S, K, T>): Promise<void>;
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 transaction is already committed.
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
- * Overwrites any existing value in the buffer.
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 transaction instance.
65
+ * @returns The result object with success, created, updated, and deleted keys.
60
66
  */
61
- rollback(): this;
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 transaction instance.
78
+ * @returns The result object with success, created, and obsolete keys.
73
79
  */
74
- abstract commit(): Deferred<this>;
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<void>;
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(): this;
8
- _merge(child: MVCCTransaction<S, K, T>): void;
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mvcc-api",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Multiversion Concurrency Control (MVCC) API for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",