UNPKG

evelodb

Version:

A high-performance native B-tree database for Node.js. Made by Evelocore.

789 lines (648 loc) 26.1 kB
<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>