@alwatr/nitrobase-engine
Version:
Nitrobase is a blazingly fast, lightweight database built on JSON. It stores data entirely in memory for lightning-quick access, while also providing a JSON file backup for persistence. You can easily serve your data over the web using our high-performanc
448 lines (443 loc) • 16.6 kB
JavaScript
/* @alwatr/nitrobase-engine v7.8.0 */
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/main.ts
var main_exports = {};
__export(main_exports, {
AlwatrNitrobase: () => AlwatrNitrobase
});
module.exports = __toCommonJS(main_exports);
// src/alwatr-nitrobase.ts
var import_nanolib2 = require("@alwatr/nanolib");
var import_exit_hook = require("@alwatr/nanolib/exit-hook");
var import_node_fs = require("@alwatr/nanolib/node-fs");
var import_nitrobase_helper = require("@alwatr/nitrobase-helper");
var import_nitrobase_reference = require("@alwatr/nitrobase-reference");
var import_nitrobase_types = require("@alwatr/nitrobase-types");
// src/logger.ts
var import_nanolib = require("@alwatr/nanolib");
__dev_mode__: import_nanolib.packageTracer.add("@alwatr/nitrobase-engine", "7.8.0");
var logger = /* @__PURE__ */ (0, import_nanolib.createLogger)("@alwatr/nitrobase-engine");
// src/alwatr-nitrobase.ts
__dev_mode__: logger.logFileModule?.("alwatr-nitrobase");
var _AlwatrNitrobase = class _AlwatrNitrobase {
/**
* Constructs an AlwatrNitrobase instance with the provided configuration.
*
* @param config The configuration of the AlwatrNitrobase engine.
* @example
* ```typescript
* const alwatrStore = new AlwatrNitrobase({
* rootPath: './db',
* saveDebounce: 40,
* });
* ```
*/
constructor(config) {
this.config = config;
/**
* Keep all loaded nitrobase file context loaded in memory.
*/
this.cacheReferences__ = {};
var _a;
this.storeChanged_ = this.storeChanged_.bind(this);
logger.logMethodArgs?.("new", config);
(_a = this.config).defaultChangeDebounce ?? (_a.defaultChangeDebounce = 40);
this.rootDb__ = this.loadRootDb__();
(0, import_exit_hook.exitHook)(this.exitHook__.bind(this));
}
/**
* Checks if a nitrobase file with the given ID exists.
*
* @param storeId - The ID of the nitrobase file to check.
* @returns `true` if the nitrobase file exists, `false` otherwise.
* @example
* ```typescript
* if (!alwatrStore.hasStore('user1/profile')) {
* alwatrStore.defineDocument(...)
* }
* ```
*/
hasStore(storeId) {
const id_ = (0, import_nitrobase_helper.getStoreId)(storeId);
const exists = this.rootDb__.hasItem(id_);
logger.logMethodFull?.("hasStore", id_, exists);
return exists;
}
/**
* Defines a new document with the given configuration and initial data.
* If a document with the same ID already exists, an error is thrown.
*
* @param stat nitrobase file stat
* @param data initial data for the document
* @template TDoc document data type
* @example
* ```typescript
* await alwatrStore.newDocument<Order>(
* {
* name: 'profile',
* region: Region.PerUser,
* ownerId: 'user1',
* },
* {
* name: 'Ali',
* email: 'ali@alwatr.io',
* }
* );
* ```
*/
newDocument(stat, data) {
logger.logMethodArgs?.("newDocument", stat);
return this.newStoreFile_(
{
...stat,
type: import_nitrobase_types.StoreFileType.Document
},
data
);
}
/**
* Defines a new collection with the given configuration and initial data.
* If a collection with the same ID already exists, an error is thrown.
*
* @param stat nitrobase file stat
* @example
* ```typescript
* await alwatrStore.newCollection<Order>(
* {
* name: 'orders',
* region: Region.PerUser,
* ownerId: 'user1',
* }
* );
* ```
*/
newCollection(stat) {
logger.logMethodArgs?.("newCollection", stat);
return this.newStoreFile_(
{
...stat,
type: import_nitrobase_types.StoreFileType.Collection
}
);
}
/**
* Defines a AlwatrNitrobaseFile with the given configuration and initial data.
*
* @param stat nitrobase file stat
* @param data initial data for the document
*/
newStoreFile_(stat, data) {
logger.logMethodArgs?.("newStoreFile_", stat);
stat.changeDebounce ?? (stat.changeDebounce = this.config.defaultChangeDebounce);
let fileStoreRef;
if (stat.type === import_nitrobase_types.StoreFileType.Document) {
if (data === void 0) {
logger.accident("newStoreFile_", "document_data_required", stat);
throw new Error("document_data_required", { cause: stat });
}
fileStoreRef = import_nitrobase_reference.DocumentReference.newRefFromData(stat, data, this.storeChanged_);
} else if (stat.type === import_nitrobase_types.StoreFileType.Collection) {
fileStoreRef = import_nitrobase_reference.CollectionReference.newRefFromData(stat, this.storeChanged_);
} else {
logger.accident("newStoreFile_", "store_file_type_not_supported", stat);
throw new Error("store_file_type_not_supported", { cause: stat });
}
if (this.rootDb__.hasItem(fileStoreRef.id)) {
logger.accident("newStoreFile_", "store_file_already_defined", stat);
throw new Error("store_file_already_defined", { cause: stat });
}
this.rootDb__.addItem(fileStoreRef.id, stat);
this.cacheReferences__[fileStoreRef.id] = fileStoreRef;
this.storeChanged_(fileStoreRef);
}
/**
* Open a document with the given id and create and return a DocumentReference.
* If the document not exists or its not a document, an error is thrown.
*
* @template TDoc document data type
* @param documentId document id {@link StoreFileId}
* @returns document reference {@link DocumentReference}
* @example
* ```typescript
* const userProfile = await alwatrStore.openDocument<User>({
* name: 'user1/profile',
* region: Region.PerUser,
* ownerId: 'user1',
* });
* userProfile.update({name: 'ali'});
* ```
*/
async openDocument(documentId) {
const id = (0, import_nitrobase_helper.getStoreId)(documentId);
logger.logMethodArgs?.("openDocument", id);
if (Object.hasOwn(this.cacheReferences__, id)) {
const ref = this.cacheReferences__[id];
if (!(ref instanceof import_nitrobase_reference.DocumentReference)) {
logger.accident("openDocument", "document_wrong_type", id);
throw new Error("document_wrong_type", { cause: id });
}
return this.cacheReferences__[id];
}
if (!this.rootDb__.hasItem(id)) {
logger.accident("openDocument", "document_not_found", id);
throw new Error("document_not_found", { cause: id });
}
const storeStat = this.rootDb__.getItemData(id);
if (storeStat.type != import_nitrobase_types.StoreFileType.Document) {
logger.accident("openDocument", "document_wrong_type", id);
throw new Error("document_wrong_type", { cause: id });
}
const context = await this.readContext__(storeStat);
const docRef = import_nitrobase_reference.DocumentReference.newRefFromContext(context, this.storeChanged_);
this.cacheReferences__[id] = docRef;
return docRef;
}
/**
* Open a collection with the given id and create and return a CollectionReference.
* If the collection not exists or its not a collection, an error is thrown.
*
* @template TItem collection item data type
* @param collectionId collection id {@link StoreFileId}
* @returns collection reference {@link CollectionReference}
* @example
* ```typescript
* const orders = await alwatrStore.openCollection<Order>({
* name: 'orders',
* region: Region.PerUser,
* ownerId: 'user1',
* });
* orders.append({name: 'order 1'});
* ```
*/
async openCollection(collectionId) {
const id = (0, import_nitrobase_helper.getStoreId)(collectionId);
logger.logMethodArgs?.("openCollection", id);
if (Object.hasOwn(this.cacheReferences__, id)) {
const ref = this.cacheReferences__[id];
if (!(ref instanceof import_nitrobase_reference.CollectionReference)) {
logger.accident("openCollection", "collection_wrong_type", id);
throw new Error("collection_wrong_type", { cause: id });
}
return this.cacheReferences__[id];
}
if (!this.rootDb__.hasItem(id)) {
logger.accident("openCollection", "collection_not_found", id);
throw new Error("collection_not_found", { cause: id });
}
const storeStat = this.rootDb__.getItemData(id);
if (storeStat.type != import_nitrobase_types.StoreFileType.Collection) {
logger.accident("openCollection", "collection_wrong_type", id);
throw new Error("collection_not_found", { cause: id });
}
const context = await this.readContext__(storeStat);
const colRef = import_nitrobase_reference.CollectionReference.newRefFromContext(context, this.storeChanged_);
this.cacheReferences__[id] = colRef;
return colRef;
}
/**
* Unloads the nitrobase file with the given id from memory.
*
* @param storeId The unique identifier of the nitrobase file. {@link StoreFileId}
* @example
* ```typescript
* alwatrStore.unloadStore({name: 'user-list', region: Region.Secret});
* alwatrStore.hasStore({name: 'user-list', region: Region.Secret}); // true
* ```
*/
unloadStore(storeId) {
const id_ = (0, import_nitrobase_helper.getStoreId)(storeId);
logger.logMethodArgs?.("unloadStore", id_);
const ref = this.cacheReferences__[id_];
if (ref === void 0) return;
if (ref.hasUnprocessedChanges_ === true) {
ref.updateDelayed_ = false;
this.storeChanged_(ref);
}
delete this.cacheReferences__[id_];
}
/**
* Remove document or collection from nitrobase and delete the file from disk.
* If the file is not found, an error is thrown.
* If the file is not unloaded, it will be unloaded first.
* You don't need to await this method to complete unless you want to make sure the file is deleted on disk.
*
* @param storeId The ID of the file to delete. {@link StoreFileId}
* @returns A Promise that resolves when the file is deleted.
* @example
* ```typescript
* alwatrStore.removeStore({name: 'user-list', region: Region.Secret});
* alwatrStore.hasStore({name: 'user-list', region: Region.Secret}); // false
* ```
*/
async removeStore(storeId) {
const id_ = (0, import_nitrobase_helper.getStoreId)(storeId);
logger.logMethodArgs?.("removeStore", id_);
if (!this.rootDb__.hasItem(id_)) {
logger.accident("removeStore", "document_not_found", id_);
throw new Error("document_not_found", { cause: id_ });
}
const ref = this.cacheReferences__[id_];
if (ref !== void 0) {
ref.freeze = true;
ref.updateDelayed_ = false;
ref.hasUnprocessedChanges_ = false;
delete this.cacheReferences__[id_];
}
const path = (0, import_nitrobase_helper.getStorePath)(this.rootDb__.getItemData(id_));
this.rootDb__.removeItem(id_);
await import_nanolib2.delay.by(0);
try {
await (0, import_node_fs.unlink)((0, import_node_fs.resolve)(this.config.rootPath, path));
} catch (error) {
logger.error("removeStore", "remove_file_failed", error, { id: storeId, path });
}
}
/**
* Saves all changes in the nitrobase.
*
* @returns A Promise that resolves when all changes are saved.
* @example
* ```typescript
* await alwatrStore.saveAll();
* ```
*/
async saveAll() {
logger.logMethod?.("saveAll");
for (const ref of Object.values(this.cacheReferences__)) {
if (ref.hasUnprocessedChanges_ === true && ref.freeze !== true) {
ref.updateDelayed_ = false;
await this.storeChanged_(ref);
}
}
}
/**
* Reads the context from a given path or StoreFileStat object.
*
* @param path The path or StoreFileStat object from which to read the context.
* @returns A promise that resolves to the context object.
*/
async readContext__(path) {
if (typeof path !== "string") path = (0, import_nitrobase_helper.getStorePath)(path);
logger.logMethodArgs?.("readContext__", path);
logger.time?.(`readContext__time(${path})`);
const context = await (0, import_node_fs.readJson)((0, import_node_fs.resolve)(this.config.rootPath, path));
logger.timeEnd?.(`readContext__time(${path})`);
return context;
}
/**
* Writes the context to the specified path.
*
* @template T The type of the context.
* @param path The path where the context will be written.
* @param context The context to be written.
* @param sync Indicates whether the write operation should be synchronous.
* @returns A promise that resolves when the write operation is complete.
*/
writeContext__(path, context) {
if (typeof path !== "string") path = (0, import_nitrobase_helper.getStorePath)(path);
logger.logMethodArgs?.("writeContext__", path);
return (0, import_node_fs.writeJson)((0, import_node_fs.resolve)(this.config.rootPath, path), context);
}
/**
* Write nitrobase file context.
*
* @param from nitrobase file reference
* @returns A promise that resolves when the write operation is complete.
*/
async storeChanged_(from) {
logger.logMethodArgs?.("storeChanged__", from.id);
const rev = from.getStoreMeta().rev;
try {
await this.writeContext__(from.path, from.getFullContext_());
if (rev === from.getStoreMeta().rev) {
from.hasUnprocessedChanges_ = false;
}
} catch (error) {
logger.error("storeChanged__", "write_context_failed", { id: from.id, error });
}
}
/**
* Load storeFilesCollection or create new one.
*/
loadRootDb__() {
logger.logMethod?.("loadRootDb__");
const fullPath = (0, import_node_fs.resolve)(this.config.rootPath, (0, import_nitrobase_helper.getStorePath)(_AlwatrNitrobase.rootDbStat__));
if (!(0, import_node_fs.existsSync)(fullPath)) {
if (this.config.errorWhenNotInitialized === true) {
throw new Error("store_not_found", { cause: "Nitrobase not initialized" });
}
logger.banner("Initialize new alwatr-nitrobase");
return import_nitrobase_reference.CollectionReference.newRefFromData(_AlwatrNitrobase.rootDbStat__, this.storeChanged_);
}
const context = (0, import_node_fs.readJson)(fullPath, true);
return import_nitrobase_reference.CollectionReference.newRefFromContext(context, this.storeChanged_, "root-db");
}
/**
* Save all nitrobase files.
*/
exitHook__() {
logger.logMethod?.("exitHook__");
for (const ref of Object.values(this.cacheReferences__)) {
logger.logProperty?.(`StoreFile.${ref.id}.hasUnprocessedChanges`, ref.hasUnprocessedChanges_);
if (ref.hasUnprocessedChanges_ === true && ref.freeze !== true) {
logger.incident?.("exitHook__", "rescue_unsaved_context", { id: ref.id });
(0, import_node_fs.writeJson)((0, import_node_fs.resolve)(this.config.rootPath, ref.path), ref.getFullContext_(), true);
ref.hasUnprocessedChanges_ = false;
}
}
}
/**
* Get all nitrobase files.
*
* @returns all nitrobase files.
* @example
* ```typescript
* const storeList = alwatrStore.getStoreList();
* for (const nitrobase of storeList) {
* console.log(nitrobase.meta.id, nitrobase.data);
* }
*/
getStoreList() {
logger.logMethod?.("getStoreList");
return this.rootDb__.values();
}
};
/**
* The Alwatr Nitrobase version string.
*
* Use for nitrobase file format version for check compatibility.
*/
_AlwatrNitrobase.version = "7.8.0";
/**
* The root nitrobase file stat.
*/
_AlwatrNitrobase.rootDbStat__ = {
name: ".nitrobase",
region: import_nitrobase_types.Region.Secret,
type: import_nitrobase_types.StoreFileType.Collection,
extension: import_nitrobase_types.StoreFileExtension.Json,
changeDebounce: 40
};
var AlwatrNitrobase = _AlwatrNitrobase;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AlwatrNitrobase
});
//# sourceMappingURL=main.cjs.map