UNPKG

evelodb

Version:

An awesome local database management system with nodejs. Made by Evelocore. With B-tree Operations & AES Encryption.

751 lines (651 loc) โ€ข 17.2 kB
<h1 align="center"> <br> <a><img src="https://i.ibb.co/t4c363X/20240305-125417.png" width="200"></a> <br> <b>EveloDB</b> <br> </h1> <h3 align="center">An awesome local database management system with nodejs. Made by Evelocore. With B-tree Operations & AES Encryption.</h3> <br> <hr> ## Requirements - Node.js ## Table of Contents - [Installation](#installation) - [Operations](#operations) - [Encryptions](#encryptions) - [Change Config](#changeconfig) - [Testing Script](#testing) - [Use BSON binary encoded](#usebson) - [Features](#features) <br> <a id="installation"></a> # ๐Ÿ“ฅ Installation ### Via npm ```bash npm i evelodb ``` ### Manual Installation - Download `evelodb.js` - Place it in your project directory - First run creates `./evelodatabase/` automatically ## Import ```js const eveloDB = require('evelodb') const db = new eveloDB(); ``` ### Optional Configuration ```js let db try { db = new eveloDB({ directory: './evelodatabase', // ./evelodatabase/users.db extension: 'db', // users.db encryption: '<encryption_method>', encryptionKey: '<encryption_key>', noRepeat: false, auroPrimaryKey: true }) } catch (err) { console.error('Init Error:', err.message); process.exit(1); } ``` ### Configuration Parameters | Parameter | Type | Description | Example | Default | |------------------|----------|------------------------------------------|-----------------------------|-----------------------------| | `directory` | string | Where database files are stored | `'./database'` | `'./evelodatabase'` | | `extension` | string | File extension for DB files | `'db'`, `'edb'` | `'json'` | | `encryption` | string | Encryption algorithm | `'aes-256-cbc'` | `null` | | `encryptionKey` | string | Key (length varies by algorithm) | 64-char hex for AES-256 | `null` | | `noRepeat` | boolean | Reject duplicate data | `true`/`false` | `false` | | `autoPrimaryKey` | string | Auto-create unique IDs (_id) | `true`/`false`/`'id'` | `true` | - ### autoPrimaryKey - `true`: Auto-create unique IDs (`_id`) for each document - `false`: No auto-create - `string`: Put your own id field name (e.g., `'id'`, `'key'`) <br><br> <a id="operations"></a> # โš™๏ธ Operations ### Create ```js // Structure db.create('collection', { key: 'value' }); // Example db.create('collection', { username: 'evelocore', name: { firstname: 'Kumuthu', lastname: 'Prabhasha' }, email: 'example@gmail.com' }); ``` > Output ```bash { success: true } ``` if `autoPrimaryKey: true` ```bash { success: true, __id: 'mcbdb90d-ajl393' } ``` if `noRepeat: true` and repeating data is detected ```bash { err: 'Duplicate data - record already exists (noRepeat enabled)', code: 'DUPLICATE_DATA' } ``` <hr> ### Update Item ```js // Structure db.edit('collection', { key: 'value' }, // find condition { key: 'new_value' } // new data ); // Example db.edit('accounts', { username: 'evelocore' }, { name: 'EveloCore Official', email: 'updated@gmail.com' } ); ``` > Output ```bash { success: true, modifiedCount: 1 } ``` if find condition not matched ```bash { err: 'No matching records found', code: 'NO_MATCH' } ``` if `noRepeat: true` and repeating data is detected ```bash { err: 'Edit would create duplicate data (noRepeat enabled)', code: 'DUPLICATE_DATA' } ``` <hr> ### Delete ```js // Structure db.delete('collection', { key: 'value' }); // Example db.delete('accounts', { username: 'evelocore' }); ``` <hr> ### Find ```js // Structure const result = db.find('collection', { key: 'value' }); // Example const user = db.find('collection', { username: 'evelocore' }); console.log(user) ``` > Output ```bash [ { username: 'evelocore', name: 'Evelocore', developer: 'K.Prabhasha', email: 'example@gmail.com' } ] ``` No result found ```bash [] ``` <hr> ### Find One ```js // Structure const result = db.findOne('collection', { key: 'value' }); // Example const user = db.findOne('collection', { username: 'evelocore' }); console.log(user) ``` > Output ```bash { username: 'evelocore', name: 'Evelocore', developer: 'K.Prabhasha', email: 'example@gmail.com' } ``` No result found ```bash null ``` <hr> ### Search ```js // Structure const result = db.search('collection', { key: 'partial_value' }); // Example const user = db.search('collection', { username: 'evelo' }); console.log(user) ``` > Output ```bash [ { username: 'evelocore', name: 'Evelocore', developer: 'K.Prabhasha', email: 'example@gmail.com' } ] ``` <hr> ### Check Existence ```js // Structure const exists = db.check('collection', { key: 'value' }); // Example const exists = db.check('accounts', { username: 'evelocore' }); console.log(exists) ``` > Output ```bash true ``` <hr> ### Count Items ```js // Structure const count = db.count('collection'); // Example const count = db.count('accounts'); console.log(count) ``` > Output ```bash { success: true, count: 25 } ``` <hr> ### Get full collection ```js // Structure const result = db.get('collection'); // Example const users = db.get('accounts'); console.log(users); ``` ### Inject full collection ```js // Structure const result = db.inject('collection', data); // Example const users = db.inject('accounts', [ { id: 1, name: 'Evelocore' }, { id: 2, name: 'K.Prabhasha' }, ]); const users = db.inject('appdata', { name: 'Evelodb', description: 'An awesome local DBMS with nodejs', author: 'Evelocore' }) // Also can maintain object using inject() and get() ``` <hr> ### Drop Collection ```js // Structure db.drop('collection'); // Example db.drop('accounts'); ``` <br><br> <a id="encryptions"></a> # ๐Ÿ” Encryptions ### Configuration ```js const eveloDB = require('evelodb') let db try { db = new eveloDB({ directory: './evelodatabase', extension: 'db', encryption: '<encryption_method>', encryptionKey: '<encryption_key>', }) } catch (err) { console.error('Init Error:', err.message); process.exit(1); } ``` ### Encryption Methods - `aes-128-cbc` (16 bytes) - 32 hex characters - `aes-192-cbc` (24 bytes) - 48 hex characters - `aes-256-cbc` (32 bytes) - 64 hex characters - `aes-128-gcm` (16 bytes) - 32 hex characters - `aes-256-gcm` (32 bytes) - 64 hex characters ### Example Configuration ```js const eveloDB = require('evelodb') let db try { db = new eveloDB({ extension: 'db', encryption: 'aes-256-cbc', encryptionKey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' // 64 hex characters }) } catch (err) { console.error('Init Error:', err.message); process.exit(1); } ``` ### Generate Hex Key Using EveloDB inbuild method ```js const length = 32 // 32, 48, 64 const key = db.generateKey(length) console.log(key) ``` Using Crypto JS ```js const crypto = require('crypto'); const key = crypto.randomBytes(16).toString('hex'); // 32 hex chars console.log(key); ``` <br><br> <a id="changeconfig"></a> # ๐Ÿ”„ Change Configuration - Note: If you are using the config, you can use the same instance of the database. - If you change the encryption / encryptionKey / extension or directory, your current db files and data was initialized with the old config will be corrupted and cannot be read. - Solution for change configuration, use `changeConfig()` method. It can change your current db config to new config and continue normally after initialize again with new config . - - Eg: Converting my current `aes-256-cbc` encrypted .json database from './evelodatabase' to `aes-128-cbc` and .db with new key and './database' directory. ```js const res = db.changeConfig({ from: { directory: './evelodatabase', extension: 'json', encryption: 'aes-256-cbc', encryptionKey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' }, to: { directory: './database', extension: 'db', encryption: 'aes-128-cbc', encryptionKey: '0123456789abcdef0123456789abcdef' // 32 hex characters }, collections: ['users', 'accounts'] // if not set collections, convert all collections }) console.log(res) // { success: true, converted: 2, failed: 0 } // Initialize again try { db = new eveloDB({ directory: './database', extension: 'db', encryption: 'aes-128-cbc', encryptionKey: '0123456789abcdef0123456789abcdef' }); } catch (err) { console.error('Re-init Error:', err.message); } ``` If you remove `encryption` and `encryptionKey` parameters in `to` object, it will remove the encryptions in your database and continue with json string. ```js const res = db.changeConfig({ from: { encryption: 'aes-256-cbc', encryptionKey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' }, to: {} }) ``` <br><br> <a id="testing"></a> # โœ… Testing with Examples - Copy `test.js` file to your project directory and run: <details> <summary><code>Show Script</code></summary> ```js // This is a test file for the EveloDB module. const eveloDB = require('evelodb'); let db // Initialize DB try { db = new eveloDB({ extension: 'db', noRepeat: true, encode: 'json', autoPrimaryKey: 'key' // auto incrementing key // Start unencrypted to test conversion }); } catch (err) { console.error('Init Error:', err.message); process.exit(1); } // ===== TEST FLOW ===== const testUser = { name: 'John Doe', age: 30 }; const query = { age: 30 }; const new_query = { age: 40 }; const search_query = { name: 'Joh' }; let createdId; // Create Data console.log('\nโœ… Create Data'); try { const res = db.create('users', testUser); createdId = res.__id; console.log(`Create Result:`, res); } catch (err) { console.error('Create Error:', err.message); } // Find Data console.log('\n๐Ÿ” Find Before Conversion'); try { const res = db.find('users', query); console.log(`Find Result:`, res); } catch (err) { console.error('Find Error:', err.message); } // Search Data console.log('\n๐Ÿ” Search by a piece of value'); try { const res = db.search('users', search_query); console.log(`Find Result:`, res); } catch (err) { console.error('Find Error:', err.message); } // Convert to encrypted format console.log('\n๐Ÿ” Convert to Encrypted Format'); try { const res = db.changeConfig({ from: {}, to: { encryption: 'aes-128-cbc', encryptionKey: '0123456789abcdef0123456789abcdef' } }); console.log('Conversion Result:', res); } catch (err) { console.error('Conversion Error:', err.message); } // Initialize with new config try { db = new eveloDB({ directory: './evelodatabase', extension: 'db', noRepeat: true, encryption: 'aes-128-cbc', encryptionKey: '0123456789abcdef0123456789abcdef' }); } catch (err) { console.error('Re-init Error:', err.message); } // Find Data Again console.log('\n๐Ÿ” Try Reading Encrypted With New DB Config'); try { const res = db.find('users', query); console.log(`Find Result:`, res); } catch (err) { console.error('Find Error:', err.message); } // Edit Data console.log('\n๐Ÿ” Editing data'); try { const res = db.edit('users', query, new_query); console.log(`Editing Result:`, res); } catch (err) { console.error('Edit Error:', err.message); } // Find Data Again console.log('\n๐Ÿ” Find old object again after edit'); try { const res = db.find('users', query); console.log(`Find Result:`, res); } catch (err) { console.error('Find Error:', err.message); } // Find Data Again console.log('\n๐Ÿ” Find new object again after edit'); try { const res = db.find('users', new_query); console.log(`Find Result:`, res); } catch (err) { console.error('Find Error:', err.message); } // Convert config back to plain JSON console.log('\n๐Ÿ”“ Convert Back to Plain JSON'); try { const res = db.changeConfig({ from: { encryption: 'aes-128-cbc', encryptionKey: '0123456789abcdef0123456789abcdef' }, to: {} }); console.log('Conversion Result:', res); } catch (err) { console.error('Conversion Error:', err.message); } // Initialize with new config try { db = new eveloDB({ extension: 'db', noRepeat: true }); } catch (err) { console.error('Re-init Error:', err.message); } // Delete Data console.log('\n๐Ÿงน Clean Up'); try { const res = db.delete('users', new_query); console.log(`Delete Result:`, res); } catch (err) { console.error('Delete Error:', err.message); } // Reset collection console.log('\n๐Ÿงน Drop Collection') try { const res = db.drop('users'); console.log(`Drop Result:`, res); } catch (err) { console.error('Drop Error:', err.message); } ``` </details> <br><br> <a id="usebson"></a> # ๐Ÿ“ Use BSON encoded > ### Binary Serialized Object Notation with EveloDB - EveloDB can handle both JSON and BSON encoded data. Here's how to use BSON encoded data with eveloDB: - JSON use string for keys, while BSON use ObjectId for keys. - โš ๏ธ Note: BSON encoding doesn't support encryption. It allways unreadable. ### Configuration with BSON encoding ```js const eveloDB = require('evelodb') let db try { db = new eveloDB({ directory: './evelodatabase', extension: 'db', // default 'bson' encode: 'bson', }) } catch (err) { console.error('Init Error:', err.message); process.exit(1); } ``` - `encode: 'bson'` for BSON encoding - `encode: 'json'` for JSON encoding (default) ## Benchmark test JSON vs BSON <details> <summary><code>Show Result</code></summary> Source code: ```js const eveloDB = require('evelodb'); let db const test = 'json' // 'json' or 'bson' try { db = new eveloDB({ extension: test, encode: test }); } catch (err) { console.error('Init Error:', err.message); process.exit(1); } const testUser = { name: 'John Doe', age: 40, photo: '[ 96MB base64 image string and test ]', } // Create Data function createData() { console.log('\nโœ… Create Data'); try { db.create('users', testUser); } catch (err) { console.error('Create Error:', err.message); } } // Find Data function findData() { let startTime = Date.now(); try { const res = db.find('users', { name: 'John Doe' }); if (res.length > 0) { const t = Date.now() - startTime; console.log(`Find Result Time taken: ${t}ms - ${test.toLocaleUpperCase()}`); } } catch (err) { console.error('Find Error:', err.message); } } for (let i = 0; i < 180; i++) { createData() } findData() ``` Result: ```bash 680KB -> Find Result Time taken: 1ms - JSON 680KB -> Find Result Time taken: 4ms - BSON 19157KB -> Find Result Time taken: 40ms - JSON 17331KB -> Find Result Time taken: 50ms - BSON ``` After trying to create 210+ data - result: ```js for (let i = 0; i < 210; i++) { createData() } ``` ```bash 20115KB -> Find Result Time taken: 41ms - JSON BSON -> Create Error: The value of "offset" is out of range. It must be >= 0 && <= 17825792. Received 17825794 ``` ## Why JSON is faster than BSON? - BSON requires binary decode `BSON.deserialize` before JS can use it. - JSON uses `JSON.parse`, which is highly optimized in V8 (Node.js engine). - BSON spec has a hard cap: 16,777,216 bytes (~16MB) per document. </details> <br><br> <a id="features"></a> # ๐Ÿ’ก Features - โœ“ JSON-based storage - โœ“ BSON-based storage - โœ“ AES Encryption - โœ“ Custom path and extension - โœ“ B-Tree indexing - โœ“ Fast retrieval - โœ“ No Repeat option - โœ“ Auto Primary Key option <hr> <br> <p align="center"> Copyright 2024 ยฉ <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>