A powerful TypeScript implementation of Software Transactional Memory (STM) using SQLite as the backing store. This library provides atomic transactions with optimistic concurrency control for managing shared state in Node.js applications.
- Features
- Installation
- Quick Start
- Core Concepts
- API Reference
- Examples
- Advanced Usage
- Contributing
- License
- 🔄 Atomic Transactions: Guaranteed atomicity for all operations
- 🛡️ Optimistic Concurrency Control: Automatic conflict detection and resolution
- 💾 SQLite Backing Store: Persistent and reliable storage
- 🗺️ JSON Path Operations: Access nested data with path-based operations
- 📝 Type Safety: Full TypeScript support with type inference
- 🔌 Multiple Database Modes: Support for both in-memory and file-based storage
- 🔄 Automatic Retries: Built-in retry mechanism for handling conflicts
- 🚀 High Performance: Optimized for concurrent operations
npm install node-stm
import { SqliteSTM } from 'node-stm';
// Create a new STM instance
const stm = new SqliteSTM();
// Create a new TVar (transactional variable)
stm.newTVar('counter', 0);
// Execute an atomic transaction
const result = stm.atomically((tx) => {
const value = tx.readTVar<number>('counter');
tx.writeTVar('counter', value + 1);
return value + 1;
});
console.log(result); // Output: 1
TVars are the fundamental building blocks of the STM system. They are:
- 🔒 Thread-safe and transactionally consistent
- 📦 Can store any JSON-serializable value
- 🔄 Automatically versioned for conflict detection
Transactions provide atomic operations with these guarantees:
- ⚡ All-or-nothing execution
- 🔄 Automatic rollback on failure
- 🛡️ Conflict detection and resolution
- 🔁 Automatic retries on conflicts
Access nested data using JSON paths:
// Read nested data
const city = tx.readTVarPath<string>('user', 'address.city');
// Update nested data
tx.updateTVarPath('user', 'preferences.theme', 'dark');
constructor(db?: number, dir?: string)
-
db
: Optional database ID (auto-generated if not provided) -
dir
: Optional directory for database storage
newTVar<T>(id: string, initialValue: T): void
Creates a new transactional variable.
atomically<T>(fn: (tx: Transaction) => T): T
Executes an atomic transaction.
newConnection(): SqliteSTM
Creates a new connection to the same database.
readTVar<T>(id: string): T
Reads a transactional variable.
writeTVar<T>(id: string, value: T): void
Writes to a transactional variable.
readTVarPath<T>(id: string, path: string): T
Reads a specific path within a JSON object.
updateTVarPath<T>(id: string, path: string, value: T): void
Updates a specific path within a JSON object.
// Create a TVar with user balances
stm.newTVar('users', {
alice: { balance: 100, transactions: [] },
bob: { balance: 50, transactions: [] },
});
// Execute a money transfer
stm.atomically((tx) => {
const aliceBalance = tx.readTVarPath<number>('users', 'alice.balance');
const bobBalance = tx.readTVarPath<number>('users', 'bob.balance');
// Transfer $30 from Alice to Bob
tx.updateTVarPath('users', 'alice.balance', aliceBalance - 30);
tx.updateTVarPath('users', 'bob.balance', bobBalance + 30);
// Record the transaction
const txId = Date.now().toString();
tx.updateTVarPath('users', 'alice.transactions', [
...tx.readTVarPath<string[]>('users', 'alice.transactions'),
`Sent $30 to Bob (${txId})`,
]);
});
// Initialize counter
stm.newTVar('counter', 0);
// Create multiple concurrent transactions
const promises = Array.from(
{ length: 10 },
() =>
new Promise<void>((resolve) => {
setTimeout(() => {
stm.atomically((tx) => {
const counter = tx.readTVar<number>('counter');
tx.writeTVar('counter', counter + 1);
});
resolve();
}, Math.random() * 10);
})
);
await Promise.all(promises);
const stm = new SqliteSTM(undefined, '/path/to/db/directory');
try {
stm.atomically((tx) => {
// Your transaction code
});
} catch (error) {
if (error.message === 'Concurrent modification detected') {
// Handle conflict
}
}
stm.atomically((tx) => {
// Outer transaction
stm.newConnection().atomically((innerTx) => {
// Inner transaction
});
});
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Haskell's STM implementation
- Built with better-sqlite3
- Thanks to all contributors who have helped shape this project