narise
v1.0.1
Published
In memory database for Node.js
Maintainers
Readme
Narise
A lightweight, schema-driven in-memory database for Node.js. Zero dependencies, pure JavaScript (ESM), with JSON persistence.
Narise keeps your data in memory for fast reads while persisting it to plain JSON files with automatic saving and backup. Define a typed schema — an order of tables — and Narise enforces types, constraints, defaults, and uniqueness, automatically correcting or rejecting invalid values.
Features
- Schema-driven tables — describe columns, types, constraints, and defaults in a single declarative order object.
- Auto value correction — every value is validated and corrected against its column definition before being stored.
- Rich column types —
string,number,boolean,enum,object,array, andtime. - Multi-key indices — primary keys, single & composite unique keys, and common (secondary) keys are indexed in memory for fast lookups.
- Auto-generated IDs — single-column primary keys are filled automatically; every row also gets an auto-incrementing
$id. - Powerful filtering — comparison operators (
$eq,$gt,$in,$regex, …), boolean logic ($and,$or,$nor,$not), and keyword matching ($text). - Joins —
innerandleftjoins on matching keys or custom functions. - Fluent CRUD —
insert,select,update,upsert, andremovethrough a chainable query API. - JSON persistence — one file per table; incremental saving only rewrites dirty tables.
- Auto save & backup — configurable save/backup intervals, plus on-demand backups.
- Schema repair — migrate existing data to a new schema, correcting fields and supplying defaults.
- Zero dependencies — built on Node.js built-ins only.
Installation
npm install nariseRequires Node.js with ES module support (the package uses "type": "module").
Quick Start
import narise from "narise";
// 1. Define an order of tables (the schema).
const order = {
user: {
primary: ["userId"],
unique: ["username"],
common: ["status"],
columns: {
userId: { type: "string", restrict: 12 },
username: { type: "string", restrict: 32 },
email: { type: "string", restrict: 128, default: "" },
status: { type: "enum", restrict: ["active", "inactive", "banned"], default: "active" },
profile: { type: "object", restrict: null, default: {} },
score: { type: "number", restrict: { integerDigits: 12, fractionDigits: 2 }, default: 0 },
verified: { type: "boolean", restrict: null, default: false },
createTime: { type: "time", restrict: null },
updateTime: { type: "time", restrict: null }
}
}
};
// 2. Deploy the database.
const db = narise({ name: "app", path: "./data" });
await db.deploy(order, { autoSaveInterval: 5000 });
// 3. Insert rows.
const users = db.table("user").insert([
{ username: "alice", score: 100, createTime: Date.now(), updateTime: Date.now() },
{ username: "bob", score: 50, createTime: Date.now(), updateTime: Date.now() }
]).exec();
// 4. Query with filters, sorting, and limits.
const result = db.table("user")
.where({ status: "active", score: { $gte: 50 } })
.sort([["score", 1]]) // a positive value means descending
.limit(10)
.select()
.exec();
// 5. Update, upsert, remove.
db.table("user").where({ username: "alice" }).update({ score: 120 }).exec();
db.table("user").upsert({ userId: users[0].userId, score: 130 }).exec();
db.table("user").where({ username: "bob" }).remove().exec();
// 6. Close (saves everything).
await db.close();Table Order (Schema)
A database is defined by an order — an object whose keys are table names and whose values describe each table.
{
"tableName": {
"primary": ["id"], // array of primary key column(s)
"unique": ["username"], // array of unique column(s); nested arrays for composite keys
"common": ["status"], // array of secondary indexed column(s)
"columns": {
"columnName": {
"type": "string", // see Column Types
"restrict": 32, // type-specific constraint (see below)
"default": "" // optional default value
}
}
}
}primary(required, non-empty) — a single-column primary is auto-filled with a random 12-character ID; a composite primary is also indexed as common columns.unique(optional) — prevents duplicates. Composite unique keys are declared as nested arrays, e.g.[["name", "scope"]].common(optional) — secondary columns indexed for fast equality filtering.columns— column definitions. Every value is corrected against its definition (type + restrict), or supplied with the default when missing.
Column Types
| Type | Description | restrict |
| --- | --- | --- |
| string | Text value | maximum length (a number); longer values are truncated |
| number | Numeric value | { integerDigits, fractionDigits }; out-of-range values are rejected |
| boolean | true / false | null |
| enum | One of a fixed set of values | array of allowed values |
| object | Any plain object | null |
| array | Any array | null |
| time | A timestamp (millisecond number) | null |
Columns without a
defaultare required — inserting a row that omits them throws an error.
Database API
narise(options) returns a Database instance.
| Method | Description |
| --- | --- |
| exists() | Whether the database has been deployed. |
| deploy(order, options) | Create and deploy a new database from an order. |
| start(options) | Open an existing database; optionally autoRepair with repairOrder / repairOptions. |
| repair(newOrder, options) | Migrate existing table data to a new schema (optional backup). |
| close() | Clear timers and save everything. |
| addTable(name, detail, persistent = true) | Add a table at runtime. |
| getTable(name) | Get the raw Table object. |
| orderTable(name, detail) | Add a table definition to the order. |
| clearTable(name) | Remove all rows from a table. |
| deleteTable(name) | Remove a table and its file. |
| table(name) | Get a chainable Query object for the table. |
Boot options (for deploy / start):
autoSaveInterval— auto-save interval in ms (default5000;0disables).autoBackupInterval— auto-backup interval in ms (default0, disabled).
Query API
Every query is fluent and chainable; call exec() to run it.
| Method | Description |
| --- | --- |
| where(method) | Filter rows using a function or a filter object. |
| join(method) | Join with another table (inner / left). |
| sort(method) | Sort by [[column, dir], …]. |
| offset(index) | Skip the first N rows. |
| limit(count) | Keep at most N rows. |
| select() | Return the resulting rows. |
| insert(values) | Insert a row or an array of rows. |
| update(values) | Update the filtered rows. |
| upsert(values) | Insert or update by primary key. |
| remove() | Remove the filtered rows. |
| count() | Number of currently matched rows. |
| exec(process) | Execute and return the result; process may transform an array result. |
Filter Operators
Simple equality works directly ({ status: "active" }). For more, use operator objects:
| Operator | Description |
| --- | --- |
| $eq / $ne | Equal / not equal |
| $gt / $gte / $lt / $lte | Comparison |
| $in / $nin | In / not in an array |
| $ins / $nins | In / not in a Set |
| $regex | RegExp match |
| $exists | Column presence |
| $and / $or / $nor | Combine sub-filters |
| $not | Negate a sub-filter |
| $text | Keyword matching, e.g. { $text: { columns: ["title"], keywords: ["hello"] } } |
You can also pass a plain function to where() for full control.
Joins
db.table("post")
.where({ status: "published" })
.join({
table: "user", // join target table
on: ["userId", "userId"], // [leftKey, rightKey], or a function (leftRow, rightRow)
as: "author", // property name on the result (defaults to the table name)
where: { status: "active" }, // optional pre-filter on the join target
type: "left" // "left" (default) or "inner"
})
.select()
.exec();Sorting
.sort([["score", 1], ["name", -1]])Note: a positive value means descending; a negative or zero value means ascending.
Persistence & Backup
Deploying a database named app in ./data produces:
data/
├── app.json # main file: { config, order }
├── app.user.json # table data (one file per table)
└── backups/ # timestamped backups (when enabled)- Incremental save — only tables marked dirty are rewritten.
- Backup —
saveBackup()orautoBackupIntervalwrites the whole database into a timestamped folder. - Repair —
repair(newOrder, { backup: true })backs up the current tables before migrating, then corrects every row against the new schema — fixing invalid values, supplying defaults for new columns, and dropping removed columns.
Running the Self-Test
index.js ships a self-contained demo (Reviser) plus a self-test that deploys a small user / post / tag database and exercises insert, filter, sort, join, update, upsert, and remove:
npm start # or: node index.jsYou should see Self-test passed. — the temporary .demo folder is cleaned up automatically.
License
MIT
