UNPKG

wise-json-db

Version:

Blazing fast, crash-proof embedded JSON database for Node.js with batch operations, TTL, indexes, and segmented checkpointing.

133 lines (121 loc) 6.38 kB
// wise-json/collection/transaction-manager.js const { v4: uuidv4 } = require('uuid'); const logger = require('../logger'); class TransactionManager { constructor(db) { this.db = db; this.txid = `txn_${uuidv4()}`; this.state = 'pending'; this._ops = []; // Теперь будет содержать { colName, type, args, ts } this._collections = {}; } collection(name) { if (!this._collections[name]) { this._collections[name] = this._createCollectionProxy(name); } return this._collections[name]; } _createCollectionProxy(name) { const self = this; return { insert(doc) { self._ops.push({ colName: name, type: 'insert', args: [doc], ts: new Date().toISOString() }); return Promise.resolve(); }, insertMany(docs) { self._ops.push({ colName: name, type: 'insertMany', args: [docs], ts: new Date().toISOString() }); return Promise.resolve(); }, update(id, updates) { self._ops.push({ colName: name, type: 'update', args: [id, updates], ts: new Date().toISOString() }); return Promise.resolve(); }, remove(id) { self._ops.push({ colName: name, type: 'remove', args: [id], ts: new Date().toISOString() }); return Promise.resolve(); }, clear() { self._ops.push({ colName: name, type: 'clear', args: [], ts: new Date().toISOString() }); return Promise.resolve(); } }; } async commit() { if (this.state !== 'pending') { throw new Error(`Transaction ${this.txid} already ${this.state}`); } this.state = 'committing'; // logger.debug(`[TransactionManager] Committing transaction ${this.txid} with ${this._ops.length} operations.`); const walManager = require('../wal-manager.js'); // Динамический require, чтобы избежать циклических зависимостей при инициализации const groupedOps = this._groupOpsByCollection(); for (const [colName, opsInCollection] of Object.entries(groupedOps)) { let collectionInstance; try { collectionInstance = await this.db.collection(colName); await collectionInstance.initPromise; // opsInCollection теперь содержит операции, каждая из которых имеет свое поле 'ts' await walManager.writeTransactionBlock(collectionInstance.walPath, this.txid, opsInCollection); } catch (err) { this.state = 'aborted'; const errMsg = `TransactionManager: WAL write failed for transaction ID '${this.txid}' in collection "${colName}": ${err.message}`; logger.error(errMsg, err.stack); // Добавим stack для большей информации throw new Error(errMsg); } } for (const op of this._ops) { // this._ops содержит операции с индивидуальными 'ts' let collectionInstance; try { collectionInstance = await this.db.collection(op.colName); // initPromise уже должен был разрешиться выше для каждой затронутой коллекции switch (op.type) { case 'insert': await collectionInstance._applyTransactionInsert(op.args[0], this.txid); break; case 'insertMany': await collectionInstance._applyTransactionInsertMany(op.args[0], this.txid); break; case 'update': await collectionInstance._applyTransactionUpdate(op.args[0], op.args[1], this.txid); break; case 'remove': await collectionInstance._applyTransactionRemove(op.args[0], this.txid); break; case 'clear': await collectionInstance._applyTransactionClear(this.txid); break; default: logger.error(`[TransactionManager] Unknown transaction operation type: ${op.type} for txid ${this.txid}`); throw new Error('Unknown transaction operation: ' + op.type); } } catch (err) { logger.error(`TransactionManager: Error applying operation (type: ${op.type}) for transaction ID '${this.txid}' in collection "${op.colName}". Error: ${err.message}`, err.stack); } } this.state = 'committed'; // logger.debug(`[TransactionManager] Transaction ${this.txid} committed successfully.`); } async rollback() { if (this.state !== 'pending') { if (this.state === 'committing' || this.state === 'committed') { throw new Error(`Transaction ${this.txid} cannot be rolled back, state is ${this.state}`); } // Если 'aborted', то повторный rollback ничего не делает, можно не бросать ошибку // logger.warn(`[TransactionManager] Rollback attempt on transaction ${this.txid} which is already ${this.state}.`); return; // Уже прервана или в процессе/завершена } // logger.debug(`[TransactionManager] Rolling back transaction ${this.txid}.`); this.state = 'aborted'; this._ops = []; } _groupOpsByCollection() { const grouped = {}; for (const op of this._ops) { // op здесь уже содержит 'ts' if (!grouped[op.colName]) { grouped[op.colName] = []; } grouped[op.colName].push(op); } return grouped; } } module.exports = TransactionManager;