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
Markdown
<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>