mvcc-api 1.0.3 → 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,14 +5,36 @@
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 is designed to be storage-agnostic via the Strategy pattern.
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
- - **Sync & Async Support**: Separate `SyncMVCCManager` and `AsyncMVCCManager` for different use cases.
14
- - **Storage Agnostic**: Implement your own `Strategy` (e.g., File System, In-Memory, Key-Value Store) to handle actual data persistence.
15
- - **Transaction Management**: Methods to `create`, `commit`, and `rollback` transactions easily.
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 |
19
+
20
+ ## Why `mvcc-api`?
21
+
22
+ It easily and powerfully solves complex concurrency problems that are difficult to handle with simple file I/O or data manipulation.
23
+
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.
35
+
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()`.
16
38
 
17
39
  ## Installation
18
40
 
@@ -22,137 +44,161 @@ npm install mvcc-api
22
44
 
23
45
  ## Usage
24
46
 
25
- ### 1. Implement a Strategy
26
-
27
- First, you need to define how data is stored by extending `MVCCStrategy`. Here is a simple example using Node.js `fs/promises`.
47
+ ### 1. Implement Strategy
28
48
 
29
49
  ```typescript
30
- import fs from 'node:fs/promises'
50
+ import fs from 'node:fs'
31
51
  import { AsyncMVCCStrategy } from 'mvcc-api'
32
52
 
33
- export class AsyncFileStrategy extends AsyncMVCCStrategy<string> {
34
- async read(key: string): Promise<string> {
35
- return fs.readFile(key, 'utf-8')
53
+ export class FileStrategy extends AsyncMVCCStrategy<string, string> {
54
+ async read(key: string) {
55
+ return fs.promises.readFile(key, 'utf-8')
36
56
  }
37
- async write(key: string, value: string): Promise<void> {
38
- await fs.writeFile(key, value, 'utf-8')
57
+ async write(key: string, value: string) {
58
+ await fs.promises.writeFile(key, value)
39
59
  }
40
- async delete(key: string): Promise<void> {
41
- await fs.unlink(key)
60
+ async delete(key: string) {
61
+ await fs.promises.unlink(key)
42
62
  }
43
- async exists(key: string): Promise<boolean> {
44
- try {
45
- await fs.access(key)
46
- return true
47
- } catch {
48
- return false
49
- }
63
+ async exists(key: string) {
64
+ return fs.existsSync(key)
50
65
  }
51
66
  }
52
67
  ```
53
68
 
54
- ### 2. Run Transactions
55
-
56
- Initialize the Manager with your Strategy and start using transactions.
69
+ ### 2. Execute Transaction
57
70
 
58
71
  ```typescript
59
- import { AsyncMVCCManager } from 'mvcc-api'
60
- import { AsyncFileStrategy } from './AsyncFileStrategy' // Your strategy
61
-
62
- async function main() {
63
- const strategy = new AsyncFileStrategy()
64
- const db = new AsyncMVCCManager(strategy)
65
-
66
- // Start a transaction
67
- const tx = db.createTransaction()
68
-
69
- try {
70
- // Write data (buffered in memory)
71
- tx.write('user:1', JSON.stringify({ name: 'Alice', balance: 100 }))
72
-
73
- // Read data (snapshot isolation)
74
- const data = await tx.read('user:1')
75
- console.log('Read within tx:', data)
76
-
77
- // Commit changes to storage
78
- await tx.commit()
79
- console.log('Transaction committed!')
80
- } catch (err) {
81
- console.error('Transaction failed:', err)
82
- tx.rollback()
83
- }
84
- }
72
+ import { AsyncMVCCTransaction } from 'mvcc-api'
85
73
 
86
- main()
87
- ```
74
+ const root = new AsyncMVCCTransaction(new FileStrategy())
75
+ const tx = root.createNested()
88
76
 
89
- ## Architecture
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
90
80
 
91
- The follow diagram illustrates the flow of a transaction in `mvcc-api`.
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>' }]
85
+
86
+ await root.commit() // Persist to storage
87
+ ```
88
+
89
+ ## Visibility Rules
92
90
 
93
91
  ```mermaid
94
92
  sequenceDiagram
95
- participant App
96
- participant Manager
97
- participant Transaction
98
- participant Strategy
99
-
100
- Note over App, Manager: Initialization
101
- App->>Manager: new Manager(Strategy)
102
-
103
- Note over App, Transaction: Start Transaction
104
- App->>Manager: createTransaction()
105
- Manager-->>Transaction: new(snapshotVersion)
106
- Manager-->>App: tx instance
107
-
108
- Note over App, Transaction: Operations
109
- App->>Transaction: read(key)
110
- Transaction->>Manager: _diskRead(key, snapshotVersion)
111
- Manager->>Manager: Check Version Index / Cache
112
- alt Data in Cache/Index
113
- Manager-->>Transaction: Return visible version
114
- else Data in Strategy
115
- Manager->>Strategy: read(key)
116
- Strategy-->>Manager: data
117
- Manager-->>Transaction: data
118
- end
119
-
120
- App->>Transaction: write(key, value)
121
- Transaction-->>Transaction: Buffer write (In-Memory)
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)
122
98
 
123
- Note over App, Strategy: Commit Phase
124
- App->>Transaction: commit()
125
- Transaction->>Manager: _commit(tx)
126
- Manager->>Manager: Check Conflicts (Optimistic Lock)
127
- alt Conflict Detected
128
- Manager-->>App: Throw Error
129
- else No Config
130
- Manager->>Strategy: write(key, value)
131
- Strategy-->>Manager: success
132
- Manager->>Manager: Update Version Index
133
- Manager-->>App: Success
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
134
103
  end
135
104
  ```
136
105
 
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.
111
+
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
+ }
127
+ ```
128
+
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.**
169
+
137
170
  ## API Reference
138
171
 
139
- ### `MVCCStrategy<T>` (Abstract)
140
- - `read(key: string): Deferred<T>`
141
- - `write(key: string, value: T): Deferred<void>`
142
- - `delete(key: string): Deferred<void>`
143
- - `exists(key: string): Deferred<boolean>`
172
+ ### `MVCCTransaction<S, K, T>`
144
173
 
145
- ### `MVCCManager<T, S>`
146
- - `createTransaction(): Transaction`
147
- - `version`: Current global version.
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` |
148
183
 
149
- ### `MVCCTransaction<T>`
150
- - `read(key: string): Deferred<T | null>`
151
- - `write(key: string, value: T): this`
152
- - `delete(key: string): this`
153
- - `commit(): Deferred<this>`
154
- - `rollback(): this`
184
+ ### `TransactionResult<K, T>`
155
185
 
156
- ## License
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
157
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
158
204
  MIT