Skip to content

Storage Layer

All storage implementations must implement the ITransactionStorage interface:

interface ITransactionStorage {
// Lifecycle
close?(): Promise<void>;
// Distributed lock
acquireLock(key: string, ttl: number): Promise<string | null>;
releaseLock(key: string, token: string): Promise<boolean>;
// Transaction log
saveTransaction(tx: TransactionLog): Promise<void>;
getTransaction(id: string): Promise<TransactionLog | null>;
updateTransactionState(id: string, state: TransactionState): Promise<void>;
updateOperationState(txId: string, opIndex: number, state: string, error?: string): Promise<void>;
getPendingTransactions(serverId?: string): Promise<TransactionLog[]>;
deleteTransaction(id: string): Promise<void>;
// Data operations
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
delete(key: string): Promise<boolean>;
}

In-memory storage, suitable for development and testing.

import { MemoryStorage } from '@esengine/transaction';
const storage = new MemoryStorage({
maxTransactions: 1000, // Maximum transaction log count
});
const manager = new TransactionManager({ storage });
  • ✅ No external dependencies
  • ✅ Fast, good for debugging
  • ❌ Data only stored in memory
  • ❌ No true distributed locking
  • ❌ Data lost on restart
// Clear all data
storage.clear();
// Get transaction count
console.log(storage.transactionCount);

Redis storage, suitable for production distributed systems. Uses factory pattern with lazy connection.

import Redis from 'ioredis';
import { RedisStorage } from '@esengine/transaction';
// Factory pattern: lazy connection, connects on first operation
const storage = new RedisStorage({
factory: () => new Redis('redis://localhost:6379'),
prefix: 'tx:', // Key prefix
transactionTTL: 86400, // Transaction log TTL (seconds)
});
const manager = new TransactionManager({ storage });
// Close connection when done
await storage.close();
// Or use await using for automatic cleanup (TypeScript 5.2+)
await using storage = new RedisStorage({
factory: () => new Redis('redis://localhost:6379')
});
// Automatically closed when scope ends
  • ✅ High-performance distributed locking
  • ✅ Fast read/write
  • ✅ Supports TTL auto-expiration
  • ✅ Suitable for high concurrency
  • ❌ Requires Redis server

Uses Redis SET NX EX for distributed locking:

// Acquire lock (atomic operation)
SET tx:lock:player:123 <token> NX EX 10
// Release lock (Lua script for atomicity)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
tx:lock:{key} - Distributed locks
tx:tx:{id} - Transaction logs
tx:server:{id}:txs - Server transaction index
tx:data:{key} - Business data

MongoDB storage, suitable for scenarios requiring persistence and complex queries. Uses shared connection from @esengine/database-drivers.

import { createMongoConnection } from '@esengine/database-drivers';
import { createMongoStorage, TransactionManager } from '@esengine/transaction';
// Create shared connection
const mongo = createMongoConnection({
uri: 'mongodb://localhost:27017',
database: 'game'
});
await mongo.connect();
// Create storage using shared connection
const storage = createMongoStorage(mongo, {
transactionCollection: 'transactions', // Transaction log collection (optional)
dataCollection: 'transaction_data', // Business data collection (optional)
lockCollection: 'transaction_locks', // Lock collection (optional)
});
// Create indexes (run on first startup)
await storage.ensureIndexes();
const manager = new TransactionManager({ storage });
// Close storage (does not close shared connection)
await storage.close();
// Shared connection can continue to be used by other modules
const userRepo = new UserRepository(mongo); // @esengine/database
// Finally close the shared connection
await mongo.disconnect();
  • ✅ Persistent storage
  • ✅ Supports complex queries
  • ✅ Transaction logs are traceable
  • ✅ Suitable for audit requirements
  • ❌ Slightly lower performance than Redis
  • ❌ Requires MongoDB server
// transactions collection
{ state: 1 }
{ 'metadata.serverId': 1 }
{ createdAt: 1 }
// transaction_locks collection
{ expireAt: 1 } // TTL index
// transaction_data collection
{ expireAt: 1 } // TTL index

Uses MongoDB unique index for distributed locking:

// Acquire lock
db.transaction_locks.insertOne({
_id: 'player:123',
token: '<token>',
expireAt: new Date(Date.now() + 10000)
});
// If key exists, check if expired
db.transaction_locks.updateOne(
{ _id: 'player:123', expireAt: { $lt: new Date() } },
{ $set: { token: '<token>', expireAt: new Date(Date.now() + 10000) } }
);
ScenarioRecommended StorageReason
Development/TestingMemoryStorageNo dependencies, fast startup
Single-machine ProductionRedisStorageHigh performance, simple
Distributed SystemRedisStorageTrue distributed locking
Audit RequiredMongoStoragePersistent logs
Mixed RequirementsRedis + MongoRedis for locks, Mongo for logs

Implement ITransactionStorage interface to create custom storage:

import { ITransactionStorage, TransactionLog, TransactionState } from '@esengine/transaction';
class MyCustomStorage implements ITransactionStorage {
async acquireLock(key: string, ttl: number): Promise<string | null> {
// Implement distributed lock acquisition
}
async releaseLock(key: string, token: string): Promise<boolean> {
// Implement distributed lock release
}
async saveTransaction(tx: TransactionLog): Promise<void> {
// Save transaction log
}
// ... implement other methods
}