evelodb
Version:
A high-performance native B-tree database for Node.js. Made by Evelocore.
789 lines (648 loc) • 26.1 kB
Markdown
<h1 align="center">
<br>
<a><img src="https://cdn.evelocore.com/files/Evelocore/projects/evelodb/icon.png" width="200"></a>
<br>
<b>EveloDB</b>
<br>
</h1>
<h3 align="center">A high-performance native B-tree database for Node.js applications.</h3>
<br>
<hr>
### 📚 Docs here [https://evelodb.evelocore.com](https://evelodb.evelocore.com)
### 🤖 Download [AGENTS.md](https://evelodb.evelocore.com/AGENTS.md) for AI Agents
<br>
## 🐵 Introduction
**EveloDB** is a high-performance native B-Tree database for large scale Node.js. It's designed for large-scale applications that need fast indexing and reliable local storage without the complexity of configuration.
> ## 📌 NOTICE
> If you are using **evelodb@1.4.10** or lower, please migrate to **evelodb-lite@1.0.1** to keep old database files and syntaxes. [Learn More](https://evelodb.evelocore.com/#migration-notice)
## Requirements
- Node.js
## Table of Contents
- [📥 Installation](#installation)
- [📘 TypeScript / ES Modules](#typescript)
- [🔢 Comparison Operators](#comparison-operators)
- [💈 Atomic Update Operators](#atomic-operators)
- [🛠️ Configuration](#configuration)
- [⚙️ Operations](#operations)
- [💉 Inject Data](#inject)
- [🔍 Get Query Result](#query-result)
- [💾 Backup Collection](#backup)
- [📦 Object Store](#objectstore)
- [🔒 Transactions (atomic)](#transactions)
- [📁 Store Files](#filehandle)
- [🖼️ Image Utilities](#filehandleimg)
- [💡 Features](#features)
- [📈 Changelog](#changelog)
<br>
<a id="installation"></a>
# 📥 Installation
### Npm Install
```bash
npm i evelodb
```
## Import
### CommonJS
```js
const eveloDB = require('evelodb');
const db = new eveloDB();
```
### TypeScript / ES Modules
```typescript
import eveloDB from 'evelodb';
const db = new eveloDB();
```
<a id="configuration"></a>
### Configuration
> ⚠️
> **CRITICAL: Use a Single Instance**
> Do **NOT** initialize `new eveloDB()` multiple times in different files (e.g., in different routes or middleware). Doing so will create separate, desynchronized memory caches and file handles, leading to missing data and `EPERM` lock errors.
> **Instead, create a single `db.js` or `db.ts` file, initialize EveloDB there, and export the instance to use throughout your application.**
```js
const db = new eveloDB({
directory: './evelodbprime', // Storage directory
noRepeat: false, // Reject duplicate data
schema: {
users: {
fields: {
username: { type: String, required: true, min: 5, max: 30 },
email: { type: String, required: true },
age: { type: Number, required: true, max: 90 },
vehicle: {
type: {
color: { type: String, required: true },
model: { type: String, required: true }
},
required: false
}
},
indexes: ["email", "username"],
uniqueKeys: ["email", "username"],
objectIdKey: "userId"
},
products: {
fields: {
name: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
inStock: { type: Boolean, required: true }
},
indexes: ["name"],
uniqueKeys: ["name"],
objectIdKey: "productId"
}
}
});
export default db;
```
### Configuration Parameters
| Parameter | Type | Required | Description | Default |
|--------------------|----------|----------|----------------------------------------------|-----------------------------|
| `directory` | string | No | Where database files are stored | `'./evelodbprime'` |
| `maxHandles` | number | No | Max open collection handles (LRU) | `64` |
| `compactThreshold` | number | No | Auto-compact ratio (0.1 - 0.9) | `0.3` |
| `schema` | Object | No | Schema, Indexes, and Unique Keys for collections | `{}` |
### Schema Definition
When defining a `schema`, each collection can have `fields`, `indexes`, and `uniqueKeys`.
#### Field Validation
| Property | Type | Description | Required |
|------------|--------------------|-----------------------------------------------------------------------------|----------|
| `type` | Constructor / Obj | Data type (e.g., `String`, `Number`, `Boolean`, or a nested object schema). | **Yes** |
| `required` | boolean | If `true`, the field must be present during creation/update. | No |
| `min` | number | Minimum value for `Number` or minimum length for `String`. | No |
| `max` | number | Maximum value for `Number` or maximum length for `String`. | No |
#### Collection Options
| Option | Type | Description | Required |
|--------------|----------|-----------------------------------------------------------------------------|----------|
| `fields` | Object | Field validation rules (as defined in the table above). | No |
| `indexes` | string[] | Fields to create B-Tree indexes for (enables O(log n) searches). | No |
| `uniqueKeys` | string[] | Fields that must contain unique values across the entire collection. | No |
| `objectIdKey`| string | Virtual name for the internal `_id` field (e.g., `"userId"`). | No |
| `noRepeat` | boolean | If `true` (default), rejects insertions of exact duplicate records. | No |
> [!IMPORTANT]
> **System Managed Fields:** Fields like `_id` (or your custom `objectIdKey`), `_createdAt`, and `_modifiedAt` are automatically managed by EveloDB. Any attempt to manually set or update these fields in `create()` or `edit()` will result in an error.
> **Note:** All parameters are optional. If no directory is specified, EveloDB will default to `./evelodbprime`.
> **Note:** EveloDB Prime uses `.db` extension and `_id` as the primary key. Secondary indexes use `.field.bidx` files.
> ### Easy Schema Explanation
> #### indexes: ["email", "username"]
> - These indexes will be created as B-Trees on the disk for faster searching
> - If not specified, it will default to [objectIdKey] or ["_id"]
>
> #### uniqueKeys: ["email", "username"]
> - These fields will be checked for uniqueness before insertion
> - If not specified, it will default to []
>
> #### objectIdKey: "userId"
> - This field will be the auto generated id
> - If not specified, it will default to '_id'
>
> #### name: { type: String, required: true }
> - 'name' is String value and required
>
> #### username: { type: String, required: true, min: 5, max: 30 }
> - 'username' is String value and required
> - minimum length is 5
> - maximum length is 30
>
> #### age: { type: Number, required: true, max: 90 }
> - 'age' is Number value and required
> - maximum value is 90
>
> #### vehicle: { type: { color: { type: String, required: true }, model: { type: String, required: true } } }
> - 'vehicle' is Object value and not required
> - inside 'vehicle' there is 'color' and 'model' which are String value and required
<br><br>
<a id="typescript"></a>
# 📘 TypeScript / ES Modules
EveloDB Prime is fully written in TypeScript and supports both CommonJS and ES Module environments natively.
### Importing Types
```typescript
import eveloDB, { type EveloDBConfig } from 'evelodb';
const config: EveloDBConfig = {
directory: './database',
noRepeat: true
};
const db = new eveloDB(config);
```
<br><br>
<a id="comparison-operators"></a>
# 🔢 Comparison Operators
Used to filter with conditions like greater than, less than, equal, etc.
| Operator | Description | Example |
|----------|-------------------------|-----------------------------------------|
| `$eq` | Equal | `{ age: { $eq: 25 } }` |
| `$ne` | Not equal | `{ age: { $ne: 25 } }` |
| `$gt` | Greater than | `{ age: { $gt: 25 } }` |
| `$gte` | Greater than or equal | `{ age: { $gte: 25 } }` |
| `$lt` | Less than | `{ age: { $lt: 25 } }` |
| `$lte` | Less than or equal | `{ age: { $lte: 25 } }` |
| `$in` | Matches any in an array | `{ status: { $in: ["active", "pending"] } }` |
| `$nin` | Not in array | `{ status: { $nin: ["inactive"] } }` |
| `$regex` | Regular expression | `{ name: { $regex: "^Jo", $options: "i" } }` |
**Example: Using Operators**
```js
db.find('users', { age: { $gte: 25 } }).all()
```
<br><br>
<a id="atomic-operators"></a>
# 💈 Atomic Update Operators
EveloDB supports atomic operators to modify fields without manual read-modify-write cycles. This is essential for counters (like stock) in high-traffic APIs.
| Operator | Description | Example |
| :--- | :--- | :--- |
| `$inc` | Increments/decrements a numeric field | `{ $inc: { stock: -1 } }` |
| `$set` | Sets a field to a specific value | `{ $set: { status: 'active' } }` |
| `$unset` | Removes a field from the document | `{ $unset: { temporaryFlag: true } }` |
| `$push` | Appends a value to an array | `{ $push: { tags: 'new-tag' } }` |
| `$pull` | Removes a value or matching items from an array | `{ $pull: { tags: 'old-tag' } }` |
**Example: Atomic Stock Update**
```js
db.edit('products', { productId: '123' }, { $inc: { stock: -1 } });
```
<br><br>
<a id="operations"></a>
# ⚙️ Operations
> [!CAUTION]
> **Bulk Operations:** Passing an empty object `{}` as the conditions parameter in `edit()`, `delete()`, or `find()` will target **every record** in the collection.
> Example: `db.edit("users", {}, { status: "active" })` will update all users in the collection.
### Create
Adds a new record to the collection.
```js
db.create('users', {
username: 'john',
email: 'john@example.com'
})
```
> Output
```bash
{
success: true,
userId: '662e5a4e3d5a4e3d5a4e3d5a', // Renamed via objectIdKey
_createdAt: '2026-04-28T10:00:00Z',
_modifiedAt: '2026-04-28T10:00:00Z'
}
```
> **Note:** If `objectIdKey` is not defined in the schema, this field defaults to `_id`.
### Update
Modifies existing records that match the conditions.
> **Note:** `db.update()` is an alias for `db.edit()`.
```js
db.edit('users',
{ username: 'john' },
{ email: 'newemail@example.com' }
)
```
> Output
```bash
{
success: true,
modifiedCount: 1,
skippedDuplicates: 0 // If noRepeat is enabled
}
```
### Delete
Removes records that match the conditions.
```js
db.delete('users', { username: 'john' })
```
> Output
```bash
{
success: true,
deletedCount: 1
}
```
### Inject
Perform high-performance bulk data injection. Useful for migrations or importing backups.
```js
const data = [
{
username: 'alice',
email: 'alice@example.com',
_createdAt: '2026-04-29T08:50:16Z',
_modifiedAt: '2026-04-29T10:00:00Z',
_id_: '69f1c6486266d6824c7680e4'
},
// ... more records
];
// Method: 'overwrite' (default) - Clears collection before injection
db.inject("users", data);
// Method: 'merge' - Appends data to existing collection
db.inject("users", data, { method: 'merge' });
```
> [!IMPORTANT]
> **Data Integrity:** Injected data **must** include system fields: `_createdAt`, `_modifiedAt`, and the ID field (e.g., `userId` or `_id`). If a schema or `noRepeat` is defined, validation will be enforced during injection.
> Output
```bash
{ success: true, count: 2 }
```
### Find
Search for records. Returns a `QueryResult` object.
```js
// Find one using virtual ID key
const user = db.findOne('users', { userId: '662e5a4e3d5a4e3d5a4e3d5a' });
// Find many (returns QueryResult)
const result = db.find('users', { age: { $gt: 18 } });
```
### Search
Performs a case-insensitive "contains" search on fields. Useful for autocomplete or simple text matching.
```js
// Matches "John", "johnny", "Elton John", etc.
const results = db.search('users', { username: 'john' });
```
### Get
Retrieves all records from a collection. Returns a `QueryResult`.
```js
const allData = db.get('users').all();
```
### Count
Returns the total number of records in a collection.
```js
const { count } = db.count('users');
```
### Check
Checks if at least one record exists that matches the conditions. Returns `boolean`.
```js
const exists = db.check('users', { email: 'john@example.com' });
```
### Drop / Reset
Permanently deletes a collection and all its associated index files.
```js
db.drop('users');
// or
db.reset('users');
```
### Compact
Manually triggers the compaction process to reclaim storage space used by deleted or updated records.
```js
db.compact('users');
```
### Rebuild Indexes
Rebuilds all B-Tree indexes (primary and secondary) for a collection from the raw data. Useful for recovery if index files are corrupted or missing.
```js
db.rebuildIndexes('users');
```
### Close All
Closes all open collection handles and ensures all data/indexes are flushed to the disk.
```js
db.closeAll();
```
### Re-Init
Re-synchronizes the database instance with the physical files on disk. Call this after external modifications (e.g., another process updated the database files manually).
```js
db.reInit();
```
### List Collections
Returns an array of all collection names found in the database directory by scanning for `.db` files.
```js
const collections = db.listCollections();
// e.g., ["users", "posts", "comments"]
```
<br><br>
<a id="query-result"></a>
# 🔍 Get Query Result
This is a wrapper that provides chainable methods for working with query results in eveloDB. It enables pagination, sorting, and other data manipulation operations on query results.
## Overview
The Query Result returned by the following eveloDB methods:
- db.find(collection, conditions)
- db.search(collection, conditions)
- db.get(collection) `when data is an array`
## Examples
### getList
- Implements pagination by returning a subset of results.
```js
// Get first 10 users
const firstPage = db.find('users', { status: 'active' }).getList(0, 10);
// Get next 10 users (pagination)
const secondPage = db.find('users', { status: 'active' }).getList(10, 10);
// Get 5 users starting from index 20
const customPage = db.find('users', { status: 'active' }).getList(20, 5);
```
### count
- Returns the total number of items in the result set.
```js
// Get total count of active users
const totalActiveUsers = db.find('users', { status: 'active' }).count();
// Get count of search results
const searchCount = db.search('products', { name: 'phone' }).count();
// Use for pagination info
const results = db.find('orders', { status: 'pending' });
const total = results.count();
const currentPage = results.getList(0, 20);
console.log(`Showing ${currentPage.length} of ${total} results`);
```
### sort
- Sorts the results using a comparison function.
```js
// Sort by name (ascending)
const sortedByName = db.find('users', { status: 'active' })
.sort((a, b) => a.name.localeCompare(b.name))
// Sort by age (descending)
const sortedByAge = db.find('users', { status: 'active' })
.sort((a, b) => b.age - a.age)
.getList(0, 20);
// Sort by date (newest first)
const sortedByDate = db.find('posts', { published: true })
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.getList(0, 10);
```
## Method Chaining
One of the key features of QueryResult is method chaining, allowing you to combine operations:
```js
const db = new eveloDB();
// Chain multiple operations
const result = db.find('products', { category: 'electronics' })
.sort((a, b) => b.price - a.price) // Sort by price (high to low)
.getList(10, 5); // Get items 11-15
// Complex chaining example
const topExpensiveProducts = db.search('products', { name: 'laptop' })
.sort((a, b) => b.price - a.price) // Sort by price descending
.getList(0, 3); // Get top 3 most expensive
// Get count after sorting (count remains the same)
const sortedResults = db.find('users', { role: 'admin' })
.sort((a, b) => a.name.localeCompare(b.name));
const totalCount = sortedResults.count(); // Total admins
const firstPage = sortedResults.getList(0, 10); // First 10 sorted admins
```
<br><br>
<a id="backup"></a>
# 💾 Backup Collection
Export your collection data for safekeeping or migration.
> [!NOTE]
> Backups always preserve the original `_id` field, even if you have configured an `objectIdKey`. This ensures your backups remain compatible even if you change your schema configuration later.
```js
// 1. Backup as Secure Binary (Full-file XOR Encoding)
db.createBackup('users', {
type: 'binary',
path: './backups',
password: 'my_secret_password',
title: 'User Records April 2026'
});
// 2. Backup as JSON
db.createBackup('users', { type: 'json', path: './backups' });
```
> Output (`createBackup`)
```bash
{
success: true,
backupPath: './backups/users_backup_2026-04-28.backup'
}
```
# 🔍 Read Backup Info
Inspect a backup file (Metadata & Data) without performing a restore.
> **Note:** Backup type defaults to `'binary'` if not specified.
```js
const info = db.readBackupFile('./backups/users_backup.backup', 'my_secret_password');
```
> Output (`readBackupFile`)
```bash
{
success: true,
title: 'User Records April 2026',
protected: true,
schema: { ... },
length: 150,
data: [ { username: 'john', ... }, ... ],
created: 2026-04-28T10:00:00Z
}
```
# 🔄 Restore Backup Collection
Restore a collection from a previous backup.
> [!WARNING]
> Restoring a backup will overwrite current data in the collection.
```js
db.restoreBackup('users', {
type: 'binary',
file: './backups/users_backup_2026-04-28.backup',
password: 'my_secret_password'
});
```
> Output (`restoreBackup`)
```bash
{ success: true }
```
<br><br>
<a id="objectstore"></a>
# 📦 Object Store
> ### Store simple application configuration or small state objects as standalone BSON files (.objdb). Perfect for keeping app data that doesn't require complex collections or indexing.
### Write/Update Object
```js
db.object("appConfig").write({ theme: 'dark', version: '1.0.4' })
db.object("appConfig").update({ theme: 'light' }) // Merges with existing data
```
### Read Object
```js
const config = db.object("appConfig").read() // { theme: 'light', version: '1.0.4' } or null
```
### Rename/Delete
```js
db.object("appConfig").rename("userSettings")
db.object("userSettings").delete()
```
### List all objects
```js
const objects = db.object().list() // ["userSettings", "themeCache"] or []
```
<br><br>
<a id="transactions"></a>
# 🔒 Transactions (`db.atomic`)
EveloDB provides an asynchronous transaction system to prevent race conditions during complex operations that involve `await` gaps.
By using `db.atomic()`, you can ensure that a block of code runs in isolation. Any other atomic operation targeting the same collection will wait in a queue until the current one finishes.
### Usage
#### 1. Collection-Level Lock (Recommended)
Only blocks the specific collection, allowing other collections to remain fast.
```js
await db.atomic('products', async (tx) => {
const item = tx.findOne('products', { id: 'p1' });
await someAsyncLogic();
tx.edit('products', { id: 'p1' }, { stock: item.stock - 1 });
});
```
#### 2. Global Lock
Blocks all collections. Useful for migrations or multi-collection updates.
```js
await db.atomic(async (tx) => {
const user = tx.findOne('users', { id: 'u1' });
tx.edit('logs', {}, { message: `User ${user.name} logged in` });
});
```
### Why use this?
While **Atomic Operators** ($inc) are great for simple math, you need **Transactions** when:
1. You have multiple steps (Read -> Logic -> Write).
2. You have `await` calls between your database operations.
3. You want to ensure "All or Nothing" behavior.
> [!TIP]
> **Alternative: `db.transaction()`**
> If you only need to lock a single collection and don't need the `tx` object, you can use the simpler `db.transaction('collection', async () => { ... })` method.
<br><br>
<br><br>
<a id="filehandle"></a>
# 📁 File Store
> ### EveloDB is a lightweight file storage system for handling any type of file directly in your local storage.
- File Management – Read, write, and delete files easily.
- Image Utilities – Special functions to process images (resize, compress, transform, ...).
- Lightweight & Fast – No external database required, works directly with the file system.
### Store image buffer as image.jpg
```js
db.writeFile('image.jpg', imageBuffer)
```
```bash
{ success: true }
```
### Read image.jpg
```js
db.readFile('image.jpg')
```
```bash
{
success: true,
data: <Buffer ff d8 ff e0 00 10 ...>
}
```
### Delete profile.pdf
```js
db.deleteFile('profile.pdf')
```
```bash
{ success: true }
```
### List all files
```js
db.allFiles() // ['image.jpg', 'profile.pdf']
```
<br><br>
<a id="filehandleimg"></a>
# 🖼️ Image Utilities
> ### EveloDB includes built-in utilities to read and process images with ease, backed by an highly optimized performance architecture.
### ⚡ Performance Architecture
- **Zero-Decode LRU Cache**: Sub-millisecond reads. A 200MB hash-based cache skips `sharp` processing entirely on cache hits.
- **Metadata Fast Path**: Dimensions are only extracted when strictly required (pixel-budget resizing), maximizing throughput for direct format/filter conversions.
- **AVIF Concurrency Limiter**: AVIF encodes are strictly bounded by a concurrency limiter (Tune via `AVIF_CONCURRENCY` env var, defaults to 2) to eliminate CPU saturation under burst traffic.
- **Hardware-Friendly Defaults**: Disables `mozjpeg` in favor of standard `libjpeg` (3-5x faster) and reduces AVIF effort to level 2 (2-4x faster).
### ✨ Features
- Resize by maximum total pixels or exact width/height constraints
- Adjust brightness and contrast
- Apply filters (grayscale, invert, mirror, flip vertically)
- Control quality and dynamic output format based on extension (`.jpg`, `.webp`, `.avif`, `.png`, etc.)
- Output formats available directly as `Buffer` or `Base64` data URLs
### ⚙️ Parameters
| Parameter | Type | Default | Description |
|-----------------|---------|---------|-------------|
| `returnBase64` | Boolean | `true` | If `true`, returns a Base64 Data URL. Otherwise returns a `Buffer`. |
| `quality` | Number | `1` | Output quality (0.1 – 1). Lower values reduce size. |
| `pixels` | Number | `0` | Maximum total pixels. `0` = keep original size. Useful for scaling down large images. |
| `maxWidth` | Number | `null` | Maximum width in pixels. |
| `maxHeight` | Number | `null` | Maximum height in pixels. |
| `blackAndWhite` | Boolean | `false` | Converts the image to grayscale. |
| `mirror` | Boolean | `false` | Flips the image horizontally. |
| `upToDown` | Boolean | `false` | Flips the image vertically. |
| `invert` | Boolean | `false` | Inverts image colors. |
| `brightness` | Number | `1` | Brightness multiplier (`0.1 – 5`). `1` = original. |
| `contrast` | Number | `1` | Contrast multiplier (`0.1 – 5`). `1` = original. |
### Read image.jpg with preset config
```js
(async () => {
const result = await db.readImage("image.jpg", {
returnBase64: true,
quality: 0.8,
pixels: 500000,
blackAndWhite: false,
mirror: false,
upToDown: false,
invert: false,
brightness: 1,
contrast: 1
});
console.log(result)
})()
```
```bash
{
success: true,
data: "data:image/jpeg;base64,/9j/4AAQSk...",
metadata: {
filename: "image.jpg",
extension: ".jpg",
originalSize: 254399,
processingApplied: {
resized: true,
qualityReduced: true,
blackAndWhite: true,
mirrored: true,
flippedVertical: false,
inverted: false,
brightnessAdjusted: true,
contrastAdjusted: true
}
}
}
```
<br><br>
<a id="features"></a>
# 💡 Features
- **BSON Native**: Optimized for binary serialization. No JSON overhead.
- **Secure Backups**: Full-file XOR encoding for binary backups.
- **B-Tree Indexing**: O(log n) lookups for primary and secondary keys.
- **Unique Constraints**: Prevent data duplication at the database level.
- **Atomic Renames**: Crash-safe file writes using temporary staging.
- **Auto-Compaction**: Automatic reclamation of deleted record space.
- **Atomic Operators**: Support for `$inc`, `$set`, `$push`, `$pull`, and `$unset` for safe concurrent updates.
- **System Timestamps**: Automatic `_createdAt` and `_modifiedAt` management.
<br><br>
<a id="changelog"></a>
# 📈 Changelog
### v1.5.1
- **Fix**: QueryResult return { err: string } instead of [] fixed & it does not return error if.
- **New**: `reInit()` function added. Call this after external modifications to the database files to re-sync with disk.
- **New**: `listCollections()` function added. Returns an array of all collection names found in the database directory.
### v1.5.0
- **Update**: Updated EveloDB Prime to v1.5.0
- **Update**: Syntax, Config, Functions, Features changed
- Migrate old evelodb@1.4.10 to evelodb-lite
### v1.4.10
- **Migrate**: evelodb@1.4.10 = evelodb-lite@1.0.1
- If you are using **evelodb@1.4.10** or lower, please migrate your package.json to **evelodb-lite@1.0.1** to keep old syntax.
<br><br>
<p align="center">
Copyright 2026 © <a href="https://evelocore.com">Evelocore</a> - All rights reserved
</p>
<p align="center">
Developed by <a href="https://kp.evelocore.com">K.Prabhasha</a>
</p>