UNPKG

axiodb

Version:

The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor

273 lines 14.4 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Converter_helper_1 = __importDefault(require("../../Helper/Converter.helper")); const Crypto_helper_1 = require("../../Helper/Crypto.helper"); const response_helper_1 = __importDefault(require("../../Helper/response.helper")); const DocumentLoader_helper_1 = __importDefault(require("../../Helper/DocumentLoader.helper")); const FileManager_1 = __importDefault(require("../../engine/Filesystem/FileManager")); const crypto_1 = require("crypto"); // Import All Utility const Searcher_utils_1 = __importDefault(require("../../utility/Searcher.utils")); const SortData_utils_1 = __importDefault(require("../../utility/SortData.utils")); const memory_operation_1 = __importDefault(require("../../Memory/memory.operation")); const Keys_1 = require("../../config/Keys/Keys"); const ReadIndex_service_1 = require("../Index/ReadIndex.service"); const DeleteIndex_service_1 = __importDefault(require("../Index/DeleteIndex.service")); const LockManager_service_1 = __importDefault(require("../Transaction/LockManager.service")); /** * The DeleteOperation class is used to delete a document from a collection. * This class provides methods to delete a single document that matches the base query. */ class DeleteOperation { constructor(collectionName, path, baseQuery, isEncrypted = false, encryptionKey) { this.allDataWithFileName = []; this.collectionName = collectionName; this.path = path; this.baseQuery = baseQuery; this.isEncrypted = isEncrypted; this.encryptionKey = encryptionKey; this.sort = {}; this.ResponseHelper = new response_helper_1.default(); this.Converter = new Converter_helper_1.default(); this.fileManager = new FileManager_1.default(); if (this.isEncrypted === true) { if (!this.encryptionKey) { throw new Error("Encryption key must be provided when isEncrypted is true."); } this.cryptoInstance = new Crypto_helper_1.CryptoHelper(this.encryptionKey); } this.allDataWithFileName = []; // To store all data with file name } // Methods /** * Deletes a single document that matches the base query. * * This method with ACID compliance: * 1. Loads all raw data from buffers * 2. Searches for documents matching the base query * 3. Selects the first matching document (applying sort if provided) * 4. Acquires lock on the document * 5. Deletes the file associated with the selected document * 6. Releases lock * * @returns {Promise<object>} A response object containing either: * - Success: { message: "Data deleted successfully", deleteData: object } * - Error: An error message if no data found or deletion fails * * @throws Will propagate any errors from underlying operations */ deleteOne() { return __awaiter(this, void 0, void 0, function* () { var _a; const lockManager = new LockManager_service_1.default(this.path); const operationId = (0, crypto_1.randomUUID)(); const timestamp = Date.now(); let documentId = null; try { // STEP 1: Find the document first let ReadResponse; // Read Response Holder if (((_a = this.baseQuery) === null || _a === void 0 ? void 0 : _a.documentId) !== undefined) { const FilePath = [ `${this.baseQuery.documentId}${Keys_1.General.DBMS_File_EXT}`, ]; ReadResponse = yield this.LoadAllBufferRawData(FilePath); } else { const fileNames = yield new ReadIndex_service_1.ReadIndex(this.path).getFileFromIndex(this.baseQuery); if (fileNames.length > 0) { // Load File Names from Index ReadResponse = yield this.LoadAllBufferRawData(fileNames); } else { ReadResponse = yield this.LoadAllBufferRawData(); } } if (!("data" in ReadResponse)) { return this.ResponseHelper.Error(ReadResponse); } const SearchedData = yield new Searcher_utils_1.default(ReadResponse.data, true).find(this.baseQuery, "data"); if (SearchedData.length === 0) { return this.ResponseHelper.Error("No data found with the specified query"); } let selectedFirstData = SearchedData[0]; // Select the first data let fileName = selectedFirstData === null || selectedFirstData === void 0 ? void 0 : selectedFirstData.fileName; // Get the file name // Sort the data if sort is provided then select the first data for deletion if (Object.keys(this.sort).length !== 0) { const Sorter = new SortData_utils_1.default(SearchedData, this.sort); const SortedData = yield Sorter.sort("data"); // Sort the data selectedFirstData = SortedData[0]; // Select the first data fileName = selectedFirstData === null || selectedFirstData === void 0 ? void 0 : selectedFirstData.fileName; // Get the file name } documentId = fileName.split('.')[0]; // STEP 2: Acquire lock on the document (ACID: Isolation) const lockResult = yield lockManager.acquireLock(documentId, operationId, timestamp); if (!("data" in lockResult)) { return this.ResponseHelper.Error("Lock acquisition failed"); } // STEP 3: Delete the file (now safe - locked) const deleteResponse = yield this.deleteFile(fileName); if (!("data" in deleteResponse)) { return this.ResponseHelper.Error("Failed to delete data"); } // Fire-and-forget: Remove from indexes and invalidate cache asynchronously for faster response new DeleteIndex_service_1.default(this.path).RemoveFromIndex(documentId, selectedFirstData === null || selectedFirstData === void 0 ? void 0 : selectedFirstData.data).catch(() => { }); memory_operation_1.default.invalidateByDocument(this.path, documentId).catch(() => { }); return this.ResponseHelper.Success({ message: "Data deleted successfully", deleteData: selectedFirstData === null || selectedFirstData === void 0 ? void 0 : selectedFirstData.data, }); } finally { // STEP 4: Always release lock (ACID: ensures no deadlock) if (documentId) { yield lockManager.releaseLock(documentId).catch(() => { }); } } }); } /** * Deletes multiple documents that match the base query. * * This method with ACID compliance: * 1. Searches for documents matching the base query * 2. Acquires locks on ALL matching documents (ensures atomicity) * 3. Deletes each matching file * 4. Releases all locks * 5. Returns success with the deleted data or an error * * @returns {Promise<SuccessInterface | ErrorInterface>} A promise that resolves to either: * - Success with a success message and the deleted data * - Error if: * - No matching data is found * - Lock acquisition fails * - Any file deletion operation fails * - The initial buffer data loading fails */ deleteMany() { return __awaiter(this, void 0, void 0, function* () { const lockManager = new LockManager_service_1.default(this.path); const operationId = (0, crypto_1.randomUUID)(); const timestamp = Date.now(); const acquiredLocks = []; try { // STEP 1: Find all matching documents let response; const fileNames = yield new ReadIndex_service_1.ReadIndex(this.path).getFileFromIndex(this.baseQuery); if (fileNames.length > 0) { // Load File Names from Index response = yield this.LoadAllBufferRawData(fileNames); } else { response = yield this.LoadAllBufferRawData(); } if (!("data" in response)) { return this.ResponseHelper.Error(response); } const SearchedData = yield new Searcher_utils_1.default(response.data, true).find(this.baseQuery, "data"); if (SearchedData.length === 0) { return this.ResponseHelper.Error("No data found with the specified query"); } // STEP 2: Extract all document IDs and acquire locks on ALL documents const documentIds = SearchedData.map((data) => data.fileName.split('.')[0]); for (const docId of documentIds) { const lockResult = yield lockManager.acquireLock(docId, operationId, timestamp); if (!("data" in lockResult)) { // Lock acquisition failed - rollback return this.ResponseHelper.Error(`Lock acquisition failed for document ${docId}`); } acquiredLocks.push(docId); } // STEP 3: All locks acquired - now perform deletions safely const deleteIndexService = new DeleteIndex_service_1.default(this.path); for (let i = 0; i < SearchedData.length; i++) { const deleteResponse = yield this.deleteFile(SearchedData[i].fileName); if (!("data" in deleteResponse)) { return this.ResponseHelper.Error("Failed to delete data"); } // Fire-and-forget: Remove from indexes asynchronously deleteIndexService.RemoveFromIndex(SearchedData[i].fileName.split('.')[0], SearchedData[i].data).catch(() => { }); } // Fire-and-forget: Invalidate cache asynchronously memory_operation_1.default.invalidateByDocuments(this.path, documentIds).catch(() => { }); return this.ResponseHelper.Success({ message: "Data deleted successfully", deleteData: SearchedData.map((data) => data.data), }); } finally { // STEP 4: Always release ALL acquired locks (ACID: ensures no deadlock) if (acquiredLocks.length > 0) { yield lockManager.releaseAllLocks(acquiredLocks).catch(() => { }); } } }); } /** * Loads all buffer raw data from the specified directory. * * This method performs the following steps: * 1. Checks if the directory is locked. * 2. If the directory is not locked, it lists all files in the directory. * 3. Reads each file and decrypts the data if encryption is enabled. * 4. Stores the decrypted data in the `AllData` array. * 5. If the directory is locked, it unlocks the directory, reads the files, and then locks the directory again. * * @returns {Promise<SuccessInterface | ErrorInterface>} A promise that resolves to a success or error response. * * @throws {Error} Throws an error if any operation fails. */ LoadAllBufferRawData(documentIdDirectFile) { return __awaiter(this, void 0, void 0, function* () { // Use shared DocumentLoader helper (DRY - consolidates duplicated code) const result = yield DocumentLoader_helper_1.default.loadDocuments(this.path, this.encryptionKey, this.isEncrypted, documentIdDirectFile, true // Include fileName for Delete operations ); // Store result in allDataWithFileName if successful if ("data" in result) { this.allDataWithFileName = result.data; } return result; }); } /** * Deletes a file from the specified path. * * This method checks if the directory is locked before attempting to delete the file. * If the directory is locked, it tries to unlock it, delete the file, and then lock it again. * * @param fileName - The name of the file to be deleted * @returns A response object indicating success or failure * Success response: { status: true, message: "File deleted successfully" } * Error response: { status: false, message: <error message> } * @private */ deleteFile(fileName) { return __awaiter(this, void 0, void 0, function* () { // Use FileManager's DeleteFileWithLock method for proper lock management return yield this.fileManager.DeleteFileWithLock(this.path, fileName); }); } /** * to be sorted to the query * @param {object} sort - The sort to be set. * @returns {DeleteOperation} - An instance of the DeleteOperation class. */ Sort(sort) { this.sort = sort; return this; } } exports.default = DeleteOperation; //# sourceMappingURL=Delete.operation.js.map