jsonion
v2.4.1
Published
A lightweight JSON file-based database with nested data access and manipulation capabilities.
Maintainers
Readme
JSONion
Type-safe JSON file database for Node.js with deep path access, query builder, data tools, relations, and indexing.
Installation
npm install jsonionRequirements
- Node.js
>=14 - ESM project (
"type": "module") when importing directly
Quick Start
import { JSONion } from "jsonion";
const db = new JSONion("app.json");
db.set("appName", "JSONion");
db.setPath("user.profile.name", "Ali");
console.log(db.get("appName"));
console.log(db.getPath("user.profile.name"));
db.flush();TypeScript Types
import {
JSONion,
type JSONionInstance,
type JSONionData,
type JSONValue,
} from "jsonion";
const db: JSONionInstance = new JSONion("data.json");
const seed: JSONionData = {
user: { id: 1, active: true },
};
db.import(seed, false);
const user = db.get<{ id: number; active: boolean }>("user");Core Database API
db.set("key", value);
db.get("key");
db.del("key");
db.has("key");
db.keys();
db.count();
db.size();
db.all();
db.clear();
db.update("key", value);
db.find((value, key) => true);
db.findEntries((value, key) => true);
db.export();
db.import({ a: 1 }, true); // merge
db.import('{"a":1}', false); // replace
db.reload();
db.flush();
db.destroy();Utility Helpers
db.hasAll("a", "b", "c");
db.getMany("a", "b", "c");
db.deleteMany("tmpA", "tmpB");
db.isTruthy("feature.enabled");
db.isFalsy("feature.disabled");Deep Path API
db.setPath("user.profile.name", "Ali");
db.getPath("user.profile.name");
db.hasPath("user.profile.name");
db.deletePath("user.profile.name");
db.updatePath("user.profile.age", (old) => Number(old ?? 0) + 1);Nested Search and Array Path Operations
db.findDeep("email", "[email protected]");
db.pluckPath("users[*].email");
db.findInPath("users", { role: "admin", age: { $gte: 18 } });
db.pushToPath("users", { id: 1, name: "Ali" });
db.pullFromPath("users", (u) => u.disabled === true);
db.updateInPath("users", { id: 1 }, { verified: true });
db.mergePath("config", { region: "eu-west-1" });
db.copyPath("template", "current");
db.movePath("old.path", "new.path");Query Builder
const adults = db
.query("users")
.greaterThanOrEqual("age", 18)
.equals("status", "active")
.sortDesc("createdAt")
.limit(20)
.execute();Query Methods
db.query("users")
.where("age", "$gte", 18)
.equals("country", "MA")
.notEquals("banned", true)
.greaterThan("score", 60)
.lessThanOrEqual("score", 100)
.in("role", ["admin", "editor"])
.notIn("state", ["deleted", "blocked"])
.between("age", 20, 40)
.whereExists("email", true)
.matches("email", /@example\.com$/)
.contains("name", "ali")
.startsWith("username", "ali")
.endsWith("email", ".com")
.select("id", "name", "email")
.exclude("password")
.orderBy("id", "asc")
.skip(0)
.limit(10)
.execute();Also available: first(), count(), exists(), paginate(), groupBy(), distinct(), avg(), sum(), min(), max().
Data Transformer
const transformer = db.transformer();
transformer.map("users", (u) => ({ ...u, fullName: `${u.first} ${u.last}` }));
transformer.filter("users", (u) => u.active);
transformer.sortBy("users", "age", "desc");
transformer.unique("users", "email");
transformer.groupBy("users", "country", "usersByCountry");
transformer.sanitize("users", { trim: true, removeNull: true });
const csv = transformer.toCSV("users");
transformer.fromCSV(csv, "usersImported");Data Analyzer
const analyzer = db.analyzer();
analyzer.statistics("sales", "amount");
analyzer.frequency("sales", "region");
analyzer.correlation("series", "x", "y");
analyzer.outliers("sales", "amount");
analyzer.aggregate("sales", "region", {
total: { field: "amount", operation: "sum" },
count: { field: "amount", operation: "count" },
});
analyzer.profile("users");
analyzer.compare("oldUsers", "newUsers");
analyzer.detectTrend("monthly", "revenue");
analyzer.forecast("monthly", "revenue", 6);
analyzer.textAnalysis("posts", "content");Relations
const relations = db.relations();
relations.defineOneToOne("profile", "users", "profiles", "userId");
relations.defineOneToMany("posts", "users", "posts", "authorId");
relations.defineManyToMany("tags", "posts", "tags", "post_tags", "postId");
relations.with("users", "profile", "posts");
relations.withNested("users", { posts: ["tags"] });
relations.attach("tags", 10, 3);
relations.detach("tags", 10, 3);
relations.sync("tags", 10, [1, 2, 3]);
relations.has("tags", 10, 3);
relations.count("posts", 1);Indexing
const indexing = db.indexing();
indexing.createIndex("email_idx", "users", "email", true);
indexing.searchByIndex("email_idx", "[email protected]");
indexing.searchRange("price_idx", 100, 500);
indexing.searchPrefix("name_idx", "Al");
indexing.createCompoundIndex("city_country_idx", "users", ["city", "country"]);
indexing.searchByCompoundIndex("city_country_idx", ["Rabat", "MA"]);
indexing.createFullTextIndex("post_text_idx", "posts", "content");
indexing.searchFullText("post_text_idx", "json database", "AND");Manager API
import { Manager } from "jsonion";
const manager = new Manager("./data");
manager.create("users.json", { initialized: true });
manager.open("users.json");
manager.exists("users.json");
manager.list();
manager.listRecursive();
manager.info("users.json");
manager.copy("users.json", "users.backup.json"); // destination must not exist
manager.rename("users.backup.json", "users.v2.json");
manager.merge(["a.json", "b.json"], "merged.json");
manager.delete("users.v2.json");Error Handling
import { JSONionError } from "jsonion";
try {
db.set("x", BigInt(1)); // not JSON-serializable
} catch (error) {
if (error instanceof JSONionError) {
console.error(error.operation);
console.error(error.message);
}
}Best Practices
- Call
flush()before process shutdown in critical apps. - Use
setPath/getPathfor nested updates instead of get-modify-set cycles. - Use indexes for frequently queried fields.
- Use
ManagerwithbaseDirto restrict file access scope. - Keep large datasets split across multiple files by domain.
Arabic Docs
Arabic documentation is available in README_AR.md.
License
MIT
