flxdb
v2.0.0
Published
A lightweight, fast and persistent key-value database for Node.js with namespaces, TTL, JSON schema validation, transactions, watch/unwatch, backup/restore and EventEmitter support.
Downloads
137
Maintainers
Readme
📁 flxdb
flxdb is a lightweight, fast, file-based key-value (KV) database designed for modern Node.js applications.
✔ Zero-configuration
✔ Persistent JSON storage
✔ Dot-notation key paths
✔ Namespaces, TTL & schema validation
✔ Transactions with automatic rollback
✔ EventEmitter — react to any change
✔ Debounced writes & async save
✔ Full TypeScript support
🆕 What's new in v2.0.0
Fully backward-compatible. Your existing
set/getcode continues to work without any changes.
| Category | New |
|---|---|
| ⚡ Performance | Debounced save, dirty flag, TTL min-heap, async write mode |
| 🔔 Events | EventEmitter — set, delete, expire, clear, save, backup, restore, transaction:commit/rollback |
| 🔁 Transactions | transaction(fn) — atomic ops with auto rollback on error |
| 👁 Watch | watch(key, fn) / unwatch(key, fn) — react to key changes |
| ⏱ TTL | expire(key, ms) — assign TTL to existing keys; ttl(key) — check remaining time |
| 🗃 Bulk ops | bulkSet([[k,v], ...]) — multiple writes in one disk flush |
| 🔢 Math | increment(key) / decrement(key) aliases |
| 🧺 Arrays | Variadic push(key, ...values), pullWhere(key, fn) |
| 🔍 Query | filter(fn), find(fn), map(fn) over all entries |
| 🛠 Utilities | rename(old, new), copy(src, dst), size() |
| 💾 Backup | backup(path) / restore(path, overwrite?) |
| 📐 Schema | min, max, enum, match (RegExp) constraints added |
| 🏗 Custom instances | require("flxdb").FlxDBCore for multiple DB files |
📦 Installation
npm install flxdb🚀 Quick Example
const db = require("flxdb");
db.set("user.name", "Lewira");
db.add("system.uptime", 1);
console.log(db.get("user.name")); // "Lewira"
console.log(db.all()); // full database object🧩 API Reference
⭐ Core Methods
set(key, value, options?)
db.set("app.theme", "dark");
db.set("user.profile.age", 24);
// With TTL
db.set("cache.token", "abc123", { ttl: 5_000 }); // expires in 5s
// With schema validation
db.set("users.1", { id: "1", xp: 100 }, { schema: "userProfile" });set(object) — deep merge
db.set({
app: { version: "2.0.0" },
cache: { enabled: true }
});bulkSet(entries)
Write multiple key-value pairs in a single disk flush.
db.bulkSet([
["server.port", 3000],
["server.host", "localhost"],
["cache.ttl", 60_000, { ttl: 60_000 }],
]);get(key, defaultValue?)
db.get("app.version", "0.0.0");If a key has expired due to TTL,
getreturns the default value.
fetch(key, defaultValue?) — alias of get()
has(key)
db.has("cache.enabled"); // false if expiredensure(key, defaultValue)
const port = db.ensure("server.port", 8080);delete(key)
db.delete("user.session");deleteAll() — alias of clear()
🔢 Numeric Operations
db.add("metrics.requests", 1);
db.subtract("metrics.tasks", 2);
db.increment("stats.hits"); // +1
db.decrement("stats.errors"); // -1🧺 Array Operations
// Push one or more values
db.push("logs", { type: "start", at: Date.now() });
db.push("tags", "v2", "stable", "latest");
// Remove by value
db.pull("tags", "deprecated");
// Remove by condition
db.pullWhere("logs", entry => entry.type === "debug");📚 Data & Key Listing
db.all(); // deep-cloned object of entire DB
db.allArray(); // [{ key, value }, ...]
db.keys(); // ["user.name", "server.port", ...]
db.keys("user."); // keys starting with "user."
db.startsWith("cache."); // [{ key, value }, ...] with prefix
db.size(); // total leaf key count🔍 Query Helpers
// Filter entries by value
db.filter(v => typeof v === "number" && v > 100);
// → [{ key: "stats.views", value: 420 }, ...]
// Find the first matching entry
db.find(v => v?.role === "admin");
// → { key: "users.7.role", value: "admin" }
// Transform all entries
db.map((v, k) => `${k} = ${v}`);🧪 Type Checking
db.type("user.profile"); // "object"
db.type("tags"); // "array"
db.type("missing"); // "undefined"🛠 Utilities
rename(oldKey, newKey)
Renames a key, preserving its value and TTL metadata.
db.rename("user.tmp", "user.profile");copy(srcKey, dstKey)
Deep-clones a key's value to a new key.
db.copy("defaults.config", "users.123.config");🧱 Namespaces
Namespaces let you organize data into logical sections inside the same file.
const guilds = db.namespace("guilds");
const users = db.namespace("users");
guilds.set("123.premium", true);
guilds.set("123.settings.prefix", "!");
users.set("456.profile", { xp: 100, level: 3 });
guilds.get("123.premium"); // true
users.get("456.profile.level"); // 3
guilds.all(); // nested object of this namespace
guilds.allArray(); // [{ key, value }, ...]
guilds.keys(); // inner keys only
guilds.size(); // entry count
guilds.filter(v => v === true);
guilds.find(v => v?.premium);
guilds.map((v, k) => k);Internally
guilds.set("123.premium", true)becomesdb.set("guilds.123.premium", true).
⏱ TTL (Time-To-Live)
Inline TTL via set()
db.set("session.token", "xyz", { ttl: 30_000 }); // 30 secondsexpire(key, ms) — assign TTL to an existing key
db.set("user.tempBan", true);
db.expire("user.tempBan", 60_000); // ban lifts after 60sttl(key) — remaining time in ms
db.ttl("session.token"); // e.g. 22450
db.ttl("user.name"); // -1 (no TTL = lives forever)
db.ttl("missing"); // null (key does not exist)After expiration:
get(key)→ returns default valuehas(key)→false- Key is automatically removed
- The
expireevent is emitted
TTL metadata is kept in memory only — never written to the JSON file.
📏 JSON Schema Validation
db.registerSchema("userProfile", {
type: "object",
required: ["id", "xp"],
props: {
id: { type: "string" },
xp: { type: "number", min: 0, max: 99999 },
role: { type: "string", enum: ["admin", "member", "guest"] },
tag: { type: "string", match: /^[a-z]+$/ },
},
});
// ✅ Valid
db.set("users.1", { id: "1", xp: 500, role: "admin" }, { schema: "userProfile" });
// ❌ Throws — xp is a string
db.set("users.2", { id: "2", xp: "abc" }, { schema: "userProfile" });
// ❌ Throws — role not in enum
db.set("users.3", { id: "3", xp: 0, role: "superuser" }, { schema: "userProfile" });Available prop constraints: type, min, max, enum, match
🔁 Transactions
All operations inside transaction() are applied atomically. If anything throws, the entire database is rolled back to its previous state.
db.transaction(tx => {
const balance = tx.get("wallet.balance", 0);
if (balance < 50) throw new Error("Insufficient funds");
tx.set("wallet.balance", balance - 50);
tx.set("wallet.spent", tx.get("wallet.spent", 0) + 50);
});On error the
transaction:rollbackevent is emitted and the error is re-thrown.
🔔 EventEmitter
flxdb extends Node's EventEmitter. You can react to any database event.
db.on("set", (key, newVal, oldVal) => console.log(`${key} changed`));
db.on("delete", (key, oldVal) => console.log(`${key} deleted`));
db.on("expire", (key, oldVal) => console.log(`${key} expired`));
db.on("clear", () => console.log("DB cleared"));
db.on("save", () => console.log("Flushed to disk"));
db.on("transaction:commit", () => console.log("Transaction committed"));
db.on("transaction:rollback", (err) => console.error("Rolled back:", err));
db.on("backup", (path) => console.log("Backed up to", path));
db.on("restore", (path) => console.log("Restored from", path));👁 Watch
Subscribe to changes on a specific key.
const off = db.watch("user.xp", (newVal, oldVal) => {
console.log(`XP changed: ${oldVal} → ${newVal}`);
});
db.set("user.xp", 500); // triggers the watcher
off(); // stop watching💾 Backup & Restore
// Save a snapshot
db.backup("./backups/2024-01-01.json");
// Restore — deep-merge into current data
db.restore("./backups/2024-01-01.json");
// Restore — overwrite all existing data
db.restore("./backups/2024-01-01.json", true);⚙ Advanced Options
Create a custom instance with FlxDBCore for full control (e.g. multiple database files):
const { FlxDBCore } = require("flxdb");
const db = new FlxDBCore({
filePath: "./data/mydb.json", // custom file path
autosave: true, // write on every change (default: true)
indent: null, // null = minified JSON
debounceMs: 200, // batch writes within 200ms window
asyncSave: true, // non-blocking fs.writeFile
ttlIntervalMs: 30_000, // TTL sweep every 30s
});forceSave()
Bypass the debounce and flush to disk immediately.
db.forceSave();destroy()
Stop internal timers and perform a final save. Call before process exit.
process.on("exit", () => db.destroy());🗄 Storage Behavior
- All data stored in
./flxdb/flxdb.jsonby default - Writes are debounced (default 100 ms) — multiple rapid changes cost one disk write
- Dirty flag prevents unnecessary writes when data hasn't changed
- Dot-notation creates deeply nested JSON structure
- Deep object merge on
set(object) - TTL metadata is in-memory only — never written to the JSON file
- Namespaces work via key prefixes — no extra files
🛠 Suitable For
- App & server configuration
- CLI tool persistence
- Local JSON storage
- Lightweight caching with TTL
- Metrics & counters
- Discord bot data (guild/user/settings)
- Small & medium Node.js apps needing a simple KV store
🧪 Example: Structured Config
db.set({
server: { port: 3000, secure: false },
app: { mode: "production", version: "2.0.0" }
});
db.get("server.port"); // 3000
db.get("app.mode"); // "production"⭐ Support
If you like the project, feel free to leave a ⭐!
Contributions and suggestions are always welcome.
📄 License
MIT License
