giantdb
Version:
Large object database in native JavaScript, with encryption support
130 lines (129 loc) • 4.48 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GiantDB = void 0;
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_util_1 = __importDefault(require("node:util"));
const fs_adapters_1 = require("fs-adapters");
const idset_js_1 = require("./idset.js");
const iomanager_js_1 = require("./iomanager.js");
const manager_js_1 = require("./middleware/manager.js");
const change_js_1 = require("./change.js");
const item_js_1 = require("./item.js");
const cryptoRandomBytes = node_util_1.default.promisify(node_crypto_1.default.randomBytes);
/**
* @returns Resolves to the generated id.
*/
async function generateId() {
const buffer = await cryptoRandomBytes(16);
return buffer.toString('hex');
}
/**
* @param source The raw source.
* @returns An adapter for the source.
*/
function resolveSource(source) {
if (typeof source === 'string') {
return new fs_adapters_1.DirectoryAdapter(source);
}
return source ?? new fs_adapters_1.MemoryAdapter();
}
/**
* A GiantDB database.
*/
class GiantDB {
_adapter;
_middlewareManager;
_ioManager;
_idSet;
/**
* Construct a new database. The source can either be a file system adapter or
* a directory path. If no source is given, a volatile in-memory store is used.
*
* @param source An adapter or directory path.
*/
constructor(source) {
this._adapter = resolveSource(source);
this._middlewareManager = new manager_js_1.MiddlewareManager();
this._ioManager = new iomanager_js_1.IOManager(this._adapter, this._middlewareManager);
this._idSet = new idset_js_1.IdSet(async () => {
const files = await this._adapter.listFiles();
return files.filter(fileName => {
// ignore non-committed files
return !fileName.includes('.tmp') && !fileName.includes('.json');
});
});
}
async _commit(id) {
// rename the file, add its id, resolve to an Item
await this._ioManager.publish(id);
await this._idSet.add(id);
return await this.get(id);
}
async _destroy(id) {
await this._ioManager.deleteTemporary(id);
}
/**
* Register the given middleware.
*
* @param middleware The middleware object.
*/
use(middleware) {
this._middlewareManager.register(middleware);
}
/**
* Prepare a new item. This constructs a new Change object that can be written
* to and then committed, making the item available.
*
* @param options Middleware options.
* @returns A Promise that resolves to a new Change object.
*/
async create(options) {
await this._adapter.init();
const id = await generateId();
const out = await this._ioManager.createTemporary(id, options);
return new change_js_1.Change(id, out, this._commit.bind(this, id), this._destroy.bind(this, id));
}
/**
* Remove an item from this database.
*
* @param id The item's id.
* @returns A Promise that is resolved when removal is complete.
*/
async remove(id) {
await this._ioManager.delete(id);
await this._idSet.remove(id);
}
/**
* Retrieve an item in this database by id.
*
* @param id The item's id.
* @returns A Promise that resolves to the item if found.
*/
async get(id) {
// check for existence first
if (!await this._idSet.includes(id)) {
throw new Error('item does not exist: ' + id);
}
// obtain the metadata, then construct
const metadata = await this._ioManager.readMetadata(id);
return new item_js_1.Item(id, this._ioManager, metadata);
}
/**
* Iterate over all items in this database. Iteration happens sequentially. If
* the callback returns a Promise or thenable, it is awaited before continuing
* with the next item.
*
* @param callbackFn The function to execute for each item.
* @returns A Promise that is resolved when iteration is finished.
*/
async each(callbackFn) {
await this._idSet.each(async (id) => {
const item = await this.get(id);
await callbackFn(item);
});
}
}
exports.GiantDB = GiantDB;