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

306 lines 14.5 kB
"use strict"; 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 response_helper_1 = __importDefault(require("../../Helper/response.helper")); // Operations const Create_operation_1 = __importDefault(require("../CRUD Operation/Create.operation")); const Reader_operation_1 = __importDefault(require("../CRUD Operation/Reader.operation")); const Delete_operation_1 = __importDefault(require("../CRUD Operation/Delete.operation")); const Update_operation_1 = __importDefault(require("../CRUD Operation/Update.operation")); const Aggregation_Operation_1 = __importDefault(require("../Aggregation/Aggregation.Operation")); const Transaction_service_1 = __importDefault(require("../Transaction/Transaction.service")); const Session_service_1 = __importDefault(require("../Transaction/Session.service")); const outers_1 = require("outers"); // Converter const Converter_helper_1 = __importDefault(require("../../Helper/Converter.helper")); const FolderManager_1 = __importDefault(require("../../engine/Filesystem/FolderManager")); const InsertIndex_service_1 = __importDefault(require("../Index/InsertIndex.service")); const IndexCache_service_1 = require("../Index/IndexCache.service"); const memory_operation_1 = __importDefault(require("../../Memory/memory.operation")); /** * Represents a collection inside a database. */ class Collection { constructor(name, path, isEncrypted = false, cryptoInstance, encryptionKey) { this.name = name; this.path = path; this.isEncrypted = isEncrypted; this.cryptoInstance = cryptoInstance; this.Converter = new Converter_helper_1.default(); this.updatedAt = new Date().toISOString(); this.encryptionKey = encryptionKey; // Initialize the Insertion class this.Insertion = new Create_operation_1.default(this.name, this.path); this.IndexManager = new InsertIndex_service_1.default(this.path); // Initialize and eagerly load index cache for maximum query performance this.indexCache = new IndexCache_service_1.IndexCache(this.path); this.indexCache.loadAllIndexes().catch(() => { // Silent failure - indexes will load on demand from disk (cold start recovery) }); // Recover any pending transactions on initialization Transaction_service_1.default.recoverTransactions(this.path).catch(() => { // Silent recovery - log errors but don't fail collection initialization }); } /** * Get Numbers of Documents in the Collection * @returns {Promise<number>} - A promise that resolves with the number of documents in the collection. * @throws {Error} - Throws an error if the collection is empty or if there is an issue with the query. */ totalDocuments() { return __awaiter(this, void 0, void 0, function* () { // Check if the collection is empty if (!this.name) { throw new Error("Collection name cannot be empty"); } // Check if the path is empty if (!this.path) { throw new Error("Path cannot be empty"); } try { // Check if Directory Locked or not const isLocked = yield new FolderManager_1.default().IsDirectoryLocked(this.path); if ("data" in isLocked) { if (isLocked.data === false) { // List all files in the directory const files = yield new FolderManager_1.default().ListDirectory(this.path); return new response_helper_1.default().Success({ message: "Total Documents in the Collection", total: files.data.length, }); } else { // if Directory is locked then unlock it const unlockResponse = yield new FolderManager_1.default().UnlockDirectory(this.path); if ("data" in unlockResponse) { // List all files in the directory const files = yield new FolderManager_1.default().ListDirectory(this.path); // Lock the directory again const lockResponse = yield new FolderManager_1.default().LockDirectory(this.path); if ("data" in lockResponse) { return new response_helper_1.default().Success({ message: "Total Documents in the Collection", total: files.data.length, }); } else { return new response_helper_1.default().Error("Cannot lock the directory"); } } else { return new response_helper_1.default().Error("Cannot unlock the directory"); } } } else { return new response_helper_1.default().Error("Cannot access the directory"); } } catch (error) { return new response_helper_1.default().Error(error); } }); } /** * Creates a new index on the specified fields by delegating to the IndexManager. * * @remarks * This method accepts one or more field names and forwards them to IndexManager.createIndex. * Index creation behavior (such as uniqueness, ordering, or persistence) is determined by the IndexManager implementation. * * @param fieldNames - One or more field names that should be included in the new index. * @returns A promise that resolves with the result returned by IndexManager.createIndex. * @throws Any errors thrown by IndexManager.createIndex (for example, validation or persistence errors) are propagated. */ newIndex(...fieldNames) { return __awaiter(this, void 0, void 0, function* () { return yield this.IndexManager.createIndex(...fieldNames); }); } /** * Drops an index by name from the collection. * * @param indexName - The name of the index to drop. * @returns A promise that resolves with the result returned by IndexManager.dropIndex. * @throws Will throw an error if the drop operation fails (for example, if the index does not exist * or if the caller lacks necessary permissions). */ dropIndex(indexName) { return __awaiter(this, void 0, void 0, function* () { return yield this.IndexManager.dropIndex(indexName); }); } /** * Inserts a document into the collection. * @param {object} data - The data to be inserted. * @returns {Promise<any>} - A promise that resolves with the response of the insertion operation. */ insert(data) { return __awaiter(this, void 0, void 0, function* () { // Check if data is empty or not if (!data) { throw new Error("Data cannot be empty"); } // Check if data is an object or not if (typeof data !== "object") { throw new Error("Data must be an object."); } // Add the documentId to the data const documentId = yield this.Insertion.generateUniqueDocumentId(); data.documentId = documentId; // Insert the updatedAt field in schema & data data.updatedAt = this.updatedAt; // Encrypt the data if crypto is enabled if (this.cryptoInstance && this.isEncrypted) { data = yield this.cryptoInstance.encrypt(this.Converter.ToString(data)); } // Save the data first const saveResult = yield this.Insertion.Save(data, documentId); // Index insertion after save yield this.IndexManager.InsertToIndex(data); // Fire-and-forget: Invalidate cache asynchronously for faster response memory_operation_1.default.invalidateByDocument(this.path, documentId).catch(() => { }); return saveResult; }); } /** * Inserts one or multiple documents into the collection. * * @param data - A single document object or an array of document objects to be inserted. * @returns A promise that resolves to a `SuccessInterface` containing the total number of documents inserted and their IDs, * or an `ErrorInterface` if the operation fails. */ insertMany(data) { return __awaiter(this, void 0, void 0, function* () { let totalDocumentsInserted = 0; const documentIds = []; if (typeof data === "object" && !Array.isArray(data)) { const result = yield this.insert(data); if ((result === null || result === void 0 ? void 0 : result.statusCode) == outers_1.StatusCodes.OK) { documentIds.push(result.data.documentId); totalDocumentsInserted++; } } else if (Array.isArray(data)) { for (const doc of data) { const result = yield this.insert(doc); if ((result === null || result === void 0 ? void 0 : result.statusCode) == outers_1.StatusCodes.OK) { documentIds.push(result.data.documentId); totalDocumentsInserted++; } } } return new response_helper_1.default().Success({ message: "Total Documents Inserted", total: totalDocumentsInserted, id: documentIds, }); }); } /** * Reads a document from the collection. * @param {object} query - The query to be executed * @returns {Reader} - An instance of the Reader class. */ query(query) { // Check if documentId is empty or not if (!query) { throw new Error("Query cannot be empty"); } // Read the data return new Reader_operation_1.default(this.name, this.path, query, this.isEncrypted, this.encryptionKey); } /** * Initiates an aggregation operation on the collection with the provided pipeline steps. * @param {object[]} PipelineQuerySteps - The pipeline steps to be executed. * @returns {Aggregation} - An instance of the Aggregation class. * @throws {Error} Throws an error if the pipeline steps are empty. * @example * ```typescript * // Aggregate the collection to get the total count of documents * collection.aggregate([{$match: {}}, ${group: {_id: null, count: {$sum: 1}}}]).exec(); * ``` */ aggregate(PipelineQuerySteps) { // Check if Pipeline Steps is valid Array of Object if (!PipelineQuerySteps) { throw new Error("Please provide valid Pipeline Steps"); } return new Aggregation_Operation_1.default(this.name, this.path, PipelineQuerySteps, this.isEncrypted, this.encryptionKey); } /** * Initiates a delete operation on the collection with the provided query. * * @param query - The query object that specifies which documents to delete. * @returns A DeleteOperation instance that can be executed to perform the deletion. * @throws {Error} Throws an error if the query is empty. * * @example * ```typescript * // Delete all documents where age is greater than 30 * collection.delete({ age: { $gt: 30 } }); * ``` */ delete(query) { // Check if documentId is empty or not if (!query) { throw new Error("Query cannot be empty"); } // Delete the data return new Delete_operation_1.default(this.name, this.path, query, this.isEncrypted, this.encryptionKey); } update(query) { if (!query) { throw new Error("Query cannot be empty"); } return new Update_operation_1.default(this.name, this.path, query, this.isEncrypted, this.encryptionKey); } beginTransaction() { if (!this.name) { throw new Error("Collection name cannot be empty"); } if (!this.path) { throw new Error("Collection path cannot be empty"); } return new Transaction_service_1.default(this.path, this.isEncrypted, this.encryptionKey); } /** * Starts a new session for MongoDB-like transaction management. * Sessions support automatic retry, timeouts, and the withTransaction pattern. * * @param options - Optional session configuration * @returns A new Session instance * * @example * ```typescript * const session = collection.startSession(); * await session.withTransaction(async (txn) => { * txn.insert({ name: 'Alice' }); * txn.update({ name: 'Bob' }, { age: 30 }); * }); * await session.endSession(); * ``` */ startSession(options) { if (!this.name) { throw new Error("Collection name cannot be empty"); } if (!this.path) { throw new Error("Collection path cannot be empty"); } return new Session_service_1.default(this.path, this.isEncrypted, this.encryptionKey, options); } } exports.default = Collection; //# sourceMappingURL=collection.operation.js.map