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
JavaScript
"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