leaf-db
Version:
Small file-based database for node.js
417 lines (289 loc) • 8.82 kB
Markdown
<div align="center">
<img src="/assets/icon.svg" width="128" alt="leaf-db">
<h1>leaf-db</h1>
<p><b>leaf-db</b> is a simple <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> embeddable database.</p>
</div>
<div align="center">
<a href="/LICENSE">
<img alt="License GPLv3" src="https://img.shields.io/badge/license-GPLv3-blue.svg" />
</a>
<a href="https://www.npmjs.com/package/leaf-db">
<img alt="NPM" src="https://img.shields.io/npm/v/leaf-db?label=npm">
</a>
<img alt="Bundle size" src="https://img.shields.io/bundlejs/size/leaf-db">
</div>
## Features
- **Strong-typed** documents and queries.
- **Easy to embed** as it does not require an HTTP server to run.
- Can be used **in the browser** if persistent storage isn't required.
- Uses **JSON** documents.
- Tiny and **0** dependencies.
## Table of Contents
- [Getting started](#getting-started)
- [Concepts](#concepts)
- [Document](#document)
- [Keys](#keys)
- [Values](#values)
- [Persistence](#persistence)
- [Corruption](#corruption)
- [Queries](#queries)
- [Operators](#operators)
- [Number](#number)
- [`$gt`](#gt)
- [`$gte`](#gte)
- [`$lt`](#lt)
- [`$lte`](#lte)
- [String](#string)
- [`$regexp`](#regexp)
- [Array](#array)
- [`$length`](#length)
- [`$includes`](#includes)
- [Logic](#logic)
- [`$not`](#not)
- [`$and`](#and)
- [`$or`](#or)
- [API](#api)
- [`id()`](#id)
- [`docs`](#docs)
- [`open()`](#open)
- [`close()`](#close)
- [`get()`](#get)
- [`insert()`](#insert)
- [`query()`](#query)
- [`update()`](#update)
- [`delete()`](#delete)
- [`drop()`](#drop)
## Getting Started
`leaf-db` is meant as a simple database that allows for basic querying over JSON data without needing to set up a database server / connection like with MongoDB or SQLite.
Node does support working with [SQLite directly](https://nodejs.org/api/sqlite.html), if you prefer a more stable, feature-complete database.
`leaf-db` can be used in the browser if persistent read / write isn't required.
### Installation
```sh
npm i leaf-db
```
### Example
Create a database using file storage with strong-typed documents:
```TS
import LeafDB from 'leaf-db';
type Document = {
title: string
name: string
}
const db = new LeafDB<Document>();
await db.open({ name: 'db', dir: process.cwd() });
const drafts = [
{ title: 'Lady', name: 'Mipha' },
{ title: 'Young Rito Warrior', name: 'Tulin' }
]
await Promise.all(drafts.map(async draft => db.insert(draft)));
// [{ _id: <string>, title: 'Young Rito Warrior', name: 'Tulin' }]
const characters = db.query({ name: 'Tulin' });
const tulin = characters[0];
tulin.title = 'Rito Warrior';
await db.update(tulin); // Overwrite existing document
await db.close();
```
## Concepts
### Document
Leaf-db stores data as [JSON](https://www.json.org/json-en.html) documents and saves them inside a [JSONL](https://jsonlines.org/) file.
#### Keys
Document keys must be of type `string` and cannot start with `$`.
Every document is required to have an `_id` field. Leaf-db automatically creates an `_id` if the field does not exist on insertion. `_id` is required to be unique when inserting documents.
### Values
Leaf-db only supports JSON values, which is defined as:
```TS
type Json =
string |
number |
boolean |
null |
Json[] |
{ [key: string]: Json };
```
### Persistence
Leaf-db stores the database in memory by default. To make use of persistence, simply open the database.
```TS
import LeafDB from 'leaf-db';
/** Create a new database, `db.jsonl`, in process.cwd() */
const db = new LeafDB();
await db.open({ name: 'db', dir: process.cwd() });
```
### Corruption
When opening a database from storage, leaf-db will return documents that are corrupt. These documents are deleted once opened and cannot be recovered afterwards.
```TS
import LeafDB from 'leaf-db';
const db = new LeafDB({ name: 'db', dir: process.cwd() });
const corrupt = await db.open(); // Corrupt[]
```
```TS
type Corrupt = {
raw: string;
error: Error;
};
```
### Queries
Leaf-db supports both literal values and [operators](#operators). Example:
```TS
/**
* Literal query where value must equal the query value
* { name: 'tulin' } // No match
* { name: 'Mipha' } // No match
*/
const a = { name: 'Tulin' };
/**
* Objects and arrays match on partial matches
* { eras: [] } // Match
* { eras: ['era of the wilds'] } // No match
* { eras: [Sky Era'] } // No Match
*/
const b = { eras: ['Era of the Wilds'] }
```
#### Operators
Operators allow for more complex queries. Operators must always be used in combination with values.
##### Number
###### `$gt`
Is greater than
```TS
const query = { a: { $gt: 3 } };
const a = { a: 2 }; // false
const b = { a: 3 }; // false
const c = { a: 4 }; // true
```
###### `$gte`
Is greater than or equal to
```TS
const query = { a: { $gte: 3 } };
const a = { a: 2 }; // false
const b = { a: 3 }; // true
const c = { a: 4 }; // true
```
###### `$lt`
Is less than
```TS
const query = { a: { $lt: 3 } };
const a = { a: 2 }; // true
const b = { a: 3 }; // false
const c = { a: 4 }; // false
```
###### `$lte`
Is less than or equal to
```TS
const query = { a: { $lte: 3 } };
const a = { a: 2 }; // true
const b = { a: 3 }; // true
const c = { a: 4 }; // false
```
##### String
###### `$regexp`
Matches strings against RegExp
```TS
const query = { a: { $regexp: /\w+/g } }
const a = { a: '' }; // false
const b = { a: '0' }; // false
const c = { a: 'a' }; // true
```
##### Array
###### `$length`
Equal to length
```TS
const query = { a: { $length: 3 } }
const a = { a: [] }; // false
const b = { a: [1, 2, 3] }; // true
const c = { a: [1, 2, 3, 4] }; // false
```
###### `$includes`
Has value in array. Does not partial match on arrays or objects.
```TS
const query = { a: { $includes: 3 } };
const a = { a: [] }; // false
const b = { a: [1, 2, 3] }; // true
const query = { b: { $includes: [3] } };
const a = { b: [ [3] ] }; // true
const b = { b: [ [3, 4] ] }; // false
```
##### Logic
###### `$not`
Invert query
```TS
const query = { $not: { a: { $lt: 3 } } };
const a = { a: 2 }; // false
const b = { a: 4 }; // true
```
###### `$and`
Must match all queries
```TS
const query = { $and: [{ a: 2 }, { b: { $lt: 3 } }] };
const a = { a: 2, b: 2 }; // true
const b = { a: 2, b: 4 }; // false
```
###### `$or`
Matches any query
```TS
const query = { $and: [{ a: 2 }, { b: { $lt: 3 } }] };
const a = { a: 2, b: 2 }; // true
const b = { a: 2, b: 4 }; // true
```
## API
### `id()`
Generate a new, unique id with format `[timestamp]-[random]`.
```TS
import LeafDB from 'leaf-db';
const id = LeafDB.id();
```
### `docs`
Get all documents
```TS
const docs = db.docs // Doc<T>[]
```
### `open()`
Open persistent storage.
```TS
import LeafDB from 'leaf-db';
const db = new LeafDB();
const corrupted = await db.open({ name: 'db', dir: process.cwd() }); // Corrupt[]
```
### `close()`
Close persistent storage.
```TS
await db.close();
```
### `get()`
Get document by id
```TS
db.get('a'); // { _id: 'a' }
```
### `insert()`
Insert document(s) into the database. Will throw an error if duplicate `_id`'s are found.
```TS
const drafts = [{ name: 'Tulin', }, { name: 'Mipha' }];
// [{ _id: <string>, name: 'Tulin' }, { _id: <string>, name: 'Mipha' }]
const docs = await Promise.all(drafts.map(async draft => draft.insert(draft)));
```
### `query()`
Find document(s) based by [query](#queries).
```TS
// Return docs where `name` is equal to `Mipha`
const docs = db.query({ name: 'Mipha' });
// Return docs where `name` is equal to `Mipha` or where `name` is equal to `Tulin`
const docs = db.query({ $or: [{ name: 'Mipha' }, { name: 'Tulin' }] });
```
### `update()`
Update existing document. Throws if document does not exist
```TS
// Update document `a` with new name `Tulin`
const docs = db.update({ _id: 'a', name: 'Tulin' });
```
### `delete()`
Delete document by `_id`
```TS
// Delete document `a`
await db.delete('a');
```
### `drop()`
Delete all documents in the database.
```TS
await db.drop();
```
## Acknowledgements
- Icon made by [Freepik](https://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/)
- This project is inspired by [louischatriot/nedb](https://github.com/louischatriot/nedb)