JDB File-Append-Only Database
JDB is a lightweight, file-append-only, in-memory, non-blocking I/O database inspired by NeDB. It allows users to manipulate data directly using standard JavaScript code, eliminating the need for a separate query language. The current stable version is 0.5.5, indicating a relatively mature but perhaps slower-paced development cycle without explicit release cadence information. Key differentiators include its lightweight core (approx. 200 lines), direct JavaScript data manipulation, promise support, and both standalone server and in-application library modes. It operates by appending JavaScript commands to a file, which are then re-executed on startup to reconstruct the database's in-memory state. Periodically, the database file can be compacted into a single JSON object to optimize storage and startup time.
Common errors
-
ReferenceError: [variable_name] is not defined at command (eval at <anonymous> (...))
cause Attempting to access an outer-scope variable directly within the `command` function passed to `jdb.exec`. The command function executes in an isolated scope.fixPass the required data via the `data` property of the `exec` options object and access it as the second argument to your command function: `jdb.exec({ data: myValue, command: (db, receivedValue) => { db.doc.someKey = receivedValue; db.save(); } });` -
TypeError: require(...) is not a function
cause The `jdb` package exports a factory function, not a direct database instance. It must be called to instantiate the database.fixEnsure you invoke the required module by adding `()` after the `require` call: `const jdb = require('jdb')();`
Warnings
- breaking JDB's core mechanism involves persisting database operations as executable JavaScript code, which is re-evaluated on startup. This design inherently means that if the database file (.jdb) is tampered with or contains untrusted code, arbitrary code execution can occur in the Node.js process. This poses a significant security risk for applications that might receive database files from untrusted sources or operate in environments where the file system is not secured.
- gotcha When defining the `command` function for `jdb.exec`, direct access to variables from the outer scope (closure) is not permitted. The command function is serialized and executed in its own isolated context, receiving only `jdbInstance` and `data` arguments.
- gotcha For databases with a very large number of operations (many `jdb.exec` calls), startup time can be significantly affected. JDB rebuilds its in-memory state by re-executing every JavaScript command ever appended to the database file. While compaction helps reduce file size, the entire command history still needs to be replayed.
Install
-
npm install jdb -
yarn add jdb -
pnpm add jdb
Imports
- jdb
import jdb from 'jdb'; const jdb = require('jdb');const jdb = require('jdb')(); - init
jdb.init((err) => { /* ... */ });await jdb.init();
- exec
jdb.exec((db) => { db.doc.myKey = outerScopeVar; });await jdb.exec({ data: someData, command: (db, data) => db.doc.myKey = data });
Quickstart
const jdb = require('jdb')();
const path = require('path');
const fs = require('fs');
const dbFilePath = path.join(__dirname, 'temp.jdb');
const some_data = {
"name": {
"first": "Yad",
"last": "Smood"
},
"fav_color": "blue",
"languages": [
{ "name": "Chinese", "level": 10 },
{ "name": "English", "level": 8, "preferred": true },
{ "name": "Japanese", "level": 6 }
],
"height": 180,
"weight": 68
};
async function runQuickstart() {
// Clean up previous runs
if (fs.existsSync(dbFilePath)) {
fs.unlinkSync(dbFilePath);
}
try {
// Initialize JDB with a specific file path
await jdb.init({ dbPath: dbFilePath });
console.log('Database initialized at:', dbFilePath);
// Set data using exec with callback (converted to promise for sequential execution)
await new Promise((resolve, reject) => {
jdb.exec(
{
data: some_data,
command: (jdbInstance, data) => {
jdbInstance.doc.ys = data;
jdbInstance.save('saved_data'); // Save with a return value
},
callback: (err, result) => {
if (err) return reject(err);
console.log('Exec with callback result:', result); // Expected: 'saved_data'
resolve(result);
}
}
);
});
// Simple way to save data using exec with promise
await jdb.exec(some_data, (jdbInstance, data) => {
jdbInstance.doc.arr = data.languages.map((el) => el.name);
jdbInstance.save(); // Default save returns undefined on success
});
console.log('Second data save completed.');
// Get the value after operations complete from the current instance
console.log('Current instance: jdb.doc.ys.name:', jdb.doc.ys.name);
console.log('Current instance: jdb.doc.arr:', jdb.doc.arr);
// Simulate restart by creating a new JDB instance and initializing from the same file
const jdb2 = require('jdb')();
await jdb2.init({ dbPath: dbFilePath });
console.log('\n--- After simulated restart ---');
console.log('New instance: jdb2.doc.ys.name:', jdb2.doc.ys.name);
console.log('New instance: jdb2.doc.arr:', jdb2.doc.arr);
} catch (err) {
console.error('An error occurred:', err);
} finally {
// Clean up the database file
if (fs.existsSync(dbFilePath)) {
fs.unlinkSync(dbFilePath);
console.log('\nCleaned up database file:', dbFilePath);
}
}
}
runQuickstart();