node-stm
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

🚀 node-stm

TypeScript SQLite Node.js License

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.

📚 Table of Contents

✨ Features

  • 🔄 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

📦 Installation

npm install node-stm

🚀 Quick Start

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

🧠 Core Concepts

TVars (Transactional Variables)

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

Transactions provide atomic operations with these guarantees:

  • ⚡ All-or-nothing execution
  • 🔄 Automatic rollback on failure
  • 🛡️ Conflict detection and resolution
  • 🔁 Automatic retries on conflicts

Path Operations

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');

📖 API Reference

SqliteSTM Class

Constructor

constructor(db?: number, dir?: string)
  • db: Optional database ID (auto-generated if not provided)
  • dir: Optional directory for database storage

Methods

newTVar
newTVar<T>(id: string, initialValue: T): void

Creates a new transactional variable.

atomically
atomically<T>(fn: (tx: Transaction) => T): T

Executes an atomic transaction.

newConnection
newConnection(): SqliteSTM

Creates a new connection to the same database.

Transaction Class

Methods

readTVar
readTVar<T>(id: string): T

Reads a transactional variable.

writeTVar
writeTVar<T>(id: string, value: T): void

Writes to a transactional variable.

readTVarPath
readTVarPath<T>(id: string, path: string): T

Reads a specific path within a JSON object.

updateTVarPath
updateTVarPath<T>(id: string, path: string, value: T): void

Updates a specific path within a JSON object.

📝 Examples

Money Transfer Example

// 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})`,
  ]);
});

Concurrent Counter Example

// 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);

🔧 Advanced Usage

Custom Database Directory

const stm = new SqliteSTM(undefined, '/path/to/db/directory');

Handling Concurrent Modifications

try {
  stm.atomically((tx) => {
    // Your transaction code
  });
} catch (error) {
  if (error.message === 'Concurrent modification detected') {
    // Handle conflict
  }
}

Nested Transactions

stm.atomically((tx) => {
  // Outer transaction
  stm.newConnection().atomically((innerTx) => {
    // Inner transaction
  });
});

🤝 Contributing

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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by Haskell's STM implementation
  • Built with better-sqlite3
  • Thanks to all contributors who have helped shape this project

/node-stm/

    Package Sidebar

    Install

    npm i node-stm

    Weekly Downloads

    9

    Version

    1.0.1

    License

    MIT

    Unpacked Size

    23.5 kB

    Total Files

    5

    Last publish

    Collaborators

    • tluyben