mvcc-api 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,148 +5,202 @@
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
+ await tx.create('new.json', '{}') // Create new key
78
+ await tx.write('config.json', '{"v":2}') // Update existing key
79
+ await tx.delete('old.json') // Delete key
80
+ await tx.exists('config.json') // true
77
81
 
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
- ```
82
+ const result = await tx.commit()
83
+ // result.created = [{ key: 'new.json', data: '{}' }]
84
+ // result.updated = [{ key: 'config.json', data: '{"v":2}' }]
85
+ // result.deleted = [{ key: 'old.json', data: '<value before delete>' }]
94
86
 
95
- ## Architecture & Visibility
87
+ await root.commit() // Persist to storage
88
+ ```
96
89
 
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.
90
+ ## Visibility Rules
98
91
 
99
92
  ```mermaid
100
93
  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)
94
+ participant P as Parent
95
+ participant C as Child
96
+
97
+ Note over P: "key": "A" (Committed)
98
+ P->>P: write("key", "B") (Uncommitted)
109
99
 
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)
100
+ rect rgb(240,240,250)
101
+ Note over C: Child Created
102
+ C->>P: read("key") "A"
103
+ Note right of C: Cannot see parent's<br/>uncommitted data
114
104
  end
105
+ ```
115
106
 
116
- R->>S: commit()
117
- Note over S, R: Global Version 2
107
+ > [!IMPORTANT]
108
+ > **Visibility Rules**
109
+ > - Transactions can always see their own changes.
110
+ > - Children can only see **committed data** at the time of creation.
111
+ > - Snapshots are maintained even if external commits occur after creation.
118
112
 
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
113
+ ## Conflict Detection
114
+
115
+ Conflicts occur upon commit if transactions have modified the same key.
116
+
117
+ ```typescript
118
+ const parent = root.createNested()
119
+ const child = parent.createNested()
120
+
121
+ parent.write('shared', 'parent') // Parent modifies after child creation
122
+ child.write('shared', 'child') // Child modifies same key
123
+
124
+ const result = child.commit()
125
+ if (!result.success) {
126
+ console.log(result.error) // "Commit conflict: Key 'shared' was modified..."
127
+ }
124
128
  ```
125
129
 
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.
130
+ | Parent Mod | Child Mod | Result |
131
+ |:---:|:---:|:---:|
132
+ | `A` | `A` | Conflict |
133
+ | `A` | `B` | Success |
134
+
135
+ > [!TIP]
136
+ > **No Conflict on Different Keys**
137
+ >
138
+ > MVCC detects conflicts on a **Key basis**. Sibling transactions can both commit successfully if they modify **different keys**.
139
+ >
140
+ > ```typescript
141
+ > const t1 = root.createNested()
142
+ > const t2 = root.createNested()
143
+ >
144
+ > t1.create('Key1', 'data')
145
+ > t2.create('Key2', 'data') // Different key
146
+ >
147
+ > t1.commit() // Success
148
+ > t2.commit() // Success
149
+ > ```
150
+
151
+ ## Result Accumulation
152
+
153
+ When a child commits, the results are accumulated in the parent.
154
+
155
+ ```typescript
156
+ const b = a.createNested()
157
+ const c = b.createNested()
158
+
159
+ c.create('C', 'val')
160
+ const cResult = c.commit()
161
+ // cResult.created = [{ key: 'C', data: 'val' }]
162
+
163
+ b.create('B', 'val')
164
+ const bResult = b.commit()
165
+ // bResult.created = [{ key: 'C', data: 'val' }, { key: 'B', data: 'val' }]
166
+ ```
167
+
168
+ > [!NOTE]
169
+ > **Changes from rolled-back children are not passed to the parent.**
130
170
 
131
171
  ## API Reference
132
172
 
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)`
173
+ ### `MVCCTransaction<S, K, T>`
174
+
175
+ | Method | Description | Return Value |
176
+ | :--- | :--- | :--- |
177
+ | `create(key, value)` | Create new key-value | `this` |
178
+ | `write(key, value)` | Update existing key | `this` |
179
+ | `delete(key)` | Delete key | `this` |
180
+ | `read(key)` | Read value | `T \| null` |
181
+ | `exists(key)` | Check if key exists | `boolean` |
182
+ | `commit()` | Apply changes | `TransactionResult<K, T>` |
183
+ | `rollback()` | Discard changes | `TransactionResult<K, T>` |
184
+ | `createNested()` | Create child transaction | `MVCCTransaction` |
185
+
186
+ ### `TransactionResult<K, T>`
187
+
188
+ ```typescript
189
+ type TransactionEntry<K, T> = { key: K, data: T }
190
+
191
+ {
192
+ success: boolean // Success status
193
+ error?: string // Error message on failure (e.g. conflict)
194
+ created: TransactionEntry[] // Keys and values created via create()
195
+ updated: TransactionEntry[] // Keys and values updated via write()
196
+ deleted: TransactionEntry[] // Keys deleted via delete() and their previous values
197
+ }
198
+ ```
199
+
200
+ ## Contributing
201
+
202
+ `mvcc-api` aims to help anyone easily use complex concurrency control.
203
+ Bug reports, feature suggestions, and PRs are always welcome! Please feel free to leave your feedback via GitHub Issues.
150
204
 
151
205
  ## License
152
206