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