akivadb
v1.0.1-alpha.7
Published
A Lightweight Schema-Free Object-Oriented LocalDatabase for Development and Educational Purposes
Downloads
5
Readme
Install
$ npm i akivadb
Note: This package requires Node >=14.5.0
Getting Started
JS
import AkivaDB from 'akivadb'; // ES6
const AkivaDB = require('akivadb').default // ES5
const db = new AkivaDB();
db.insert({ species: 'cat', name: 'whiskers' })
.then((inserted) =>{
console.log(`added ${inserted[0].name} to the database!`)
})
.catch(console.error)
TS
import AkivaDB, { Doc } from 'akivadb';
interface Document extends Doc {
species: string,
name?: string
}
const db = new AkivaDB<Document>();
db.insert({ species: 'cat', name: 'whiskers' })
.then((inserted) =>{
console.log(`added ${inserted[0].name} to the database!`)
})
.catch(console.error)
API
- Install
- Getting Started
- API
- Database
- Inserting docs
- Finding docs
- Updating docs
- Deleting docs
- Acknowledgements
Database
Create / load
const db = new AkivaDB({ name, root, diableAutoload, strict, inMemory })
options.name
- Database name, will create in-memory if not providedoptions.root
- Database root path, will create in-memory if not providedoptions.diableAutoload
- If true,db.load
must be called to load the databaseoptions.strict
- If false, database will throw silent errorsoptions.inMemory
- If true, database will not persist to a file
// Memory-only database
const db = new AkivaDB()
// Persistent database with autoload
const db = new AkivaDB({name: 'db', root: process.cwd() });
// Persistent database with manual load
const db = new AkivaDB({ name: 'db', root: process.cwd(), disableAutoload: true })
// Loading is not neccesary, but recommended
// Not loading means the data from file isn't read,
// which can cause data loss when `persist()` is called (as it overwrites the file)
db.load()
Events
db.on('insert', (doc) => {
//Logs inserted document
console.log(doc)
})
db.on('update', (doc) => {
//Logs updated document
console.log(doc)
})
db.on('delete', (doc) => {
//Logs delete document
console.log(doc)
})
Inserting docs
await db.insert(NewDoc) => Promise<Doc>
await db.insertMany(OneOrMore<NewDoc>) => Promise<Doc[]>
Inserts document(s) into the database. _id
is automatically generated if the _id does not exist.
Note: Fields cannot start with $
(modifier field) or contain .
(dot-queries). Values cannot be undefined
.
insert
will reject on the first invalid doc if strict
is enabled, otherwise invalid docs are ignored.
Insertion takes place after all docs are validated, meaning no data will be inserted if insert
rejects.
akivadb
for now does not keep track of when docs are inserted, updated or deleted.
Example
const newDoc = {
crud: 'create',
data: [{
field: 1
}]
}
db.insert(newDoc)
.then((doc) => {
console.log(doc)
//logs doc
})
.catch (err) {
console.error(err)
}
Finding docs
Basic query
await db.findOne(Query, Projection) => Promise<Doc>
await db.find(Query, Projection) => Promise<Doc[]>
await db.findOneById(string, Projection) => Promise<Doc>
await db.findById(string | string[], Projection) => Promise<Doc>
Find doc(s) matching query. Operators and dot notation are supported and can be mixed together.
// Data
// { _id: 1, type: 'normal', important: false, variants: ['weak', 'strong'] }
// { _id: 2, type: 'normal', important: true, variants: ['weak', 'strong'] }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3 } }
// Find docs matching type 'normal'
// [1, 2] (Doc _id's)
await db.find({ type: 'normal' })
// Find all docs matching type 'normal' and important 'true'
// [2], all fields must match
await db.find({ type: 'normal', important: 'true' })
// Find all docs with variants 'weak'
// [4], note how only 4 matches, even though all entries contain weak
// Array content and order must match
await db.find({ variant: ['weak'] })
// Find all docs with variants 'strong', 'weak', in that order
// []
await db.find({ variant: ['strong', 'weak'] })
// Find all docs with parent '3'
// [], all keys must be present
await db.find({ properties: { parent: 3 } })
// [4], key order does not matter
await db.find({ properties: { parent: 3, type: 'weak' } })
Dot notation
Dot notation can be used to match nested fields
// Data
// { _id: 1, variants: ['normal', 'strong'], properties: { type: 'weak', parent: 3 } }
// { _id: 2, variants: ['strong', 'normal'], properties: { type: 'weak', parent: 3 } }
// { _id: 3, variants: [{ id: 'strong', properties: [{ type: 'weak' }] }] }
// Find all docs with properties.type 'weak'
// [1, 2]
await db.find({ 'properties.type': 'weak' })
// Find all docs where first entry of variants is `strong`
// [2]
await db.find({ 'variants.0': 'strong' })
// Find all docs where type of first entry of properties of first entry of variants is 'weak'
// [3]
await db.find({ 'variants.0.properties.0.type': 'weak' })
Operators
Operators can be used to create advanced queries. The following operators are supported:
Logic operators
$gt
- Is greater than$gte
- Is greater or equal than$lt
- Is less than$lte
- Is less or equal than$not
- Is not equal
String operators
$string
- Does string include string$stringStrict
- Does string include string, case sensitive
Object operators
$keys
- Does object have keys
Array operators
These operators will return false if the queries field is not an array
$includes
- Does array contain value$or
- Do any of the queries match
Example
// Data
// { _id: 1, type: 'normal', important: false, variants: ['weak', 'strong'] }
// { _id: 2, type: 'normal', important: true, variants: ['weak', 'strong'] }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
// { _id: 5, properties: [{ variants: ['weak', 'normal' ] }, { type: 'strong' }] }
// $gt / $gte / $lt / $lte
// [3, 4, 5]
await db.find({ $gt: { _id: 2 } })
// all fields within '$lte' must match
// [4]
await db.find({ $lte: { _id: 4, 'properties.parent': 3 }})
// $not
// [2, 3, 4, 5]
await db.find({ $not: { _id: 1 } })
// $string
// [1, 2]
await db.find({ $string: { type: 'mal' } })
// [1, 2]
await db.find({ $string: { type: 'MAL' } })
// []
await db.find({ $stringStrict: { type: 'MAL' } })
// $keys
// [1, 2, 3, 4]
await db.find({ $keys: ['type'] })
// [1, 2, 3]
await db.find({ $keys: ['type', 'important'] })
// $includes
// [1, 2, 3, 4]
await db.find({ $includes: { variants: 'weak' } })
// [4]
await db.find({ $includes: { 'properties.variants': 'strong' } })
// Error, field is not an array
await db.find({ $includes: { type: 'weak' } })
// Error, dot notation isn't a valid object field
await db.find({ $includes: { properties: { 'variants.0': 'weak' } } })
// $or
// [1, 2, 4]
await db.find({ $or: [{ type: 'weak' }, { type: 'normal' }] })
// [1, 2, 3, 4, 5]
await db.find({ $or: [{ $includes: { variants: 'weak' } }, { _id: 5 }] })
Projection
akivadb
supports projection. When using projection, only the specified fields will be returned. Empty objects can be returned if projection
is []
, or when none of the fields provided exist on the found objects.
Example
// Data
// { _id: 1, type: 'normal', important: false, variants: ['weak', 'strong'] }
// { _id: 2, type: 'normal', important: true, variants: ['weak', 'strong'] }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
// { _id: 5, properties: [{ variants: ['weak', 'normal' ] }, { type: 'strong' }] }
// [{ _id: 1 }, { _id: 2 }]
await db.find({ type: 'normal' }, ['_id'])
// [{ type: 'normal' }, { type: 'normal' }, { type: 'strong' }, { type: 'weak' }, {}]
await db.find({}, ['type'])
Indexing
akivadb
uses a hash table under the hood to store docs. All docs are indexed by _id
, meaning using any byId
query is considerably faster than its regular counterpart.
The byId
queries accept a single _id
string, or an array of _id
strings.
Updating docs
await db.updateOne(Query, Update | NewDoc) => Promise<Doc>
await db.updateMany(Query, Update | NewDoc) => Promise<Doc[]>
await db.updateOneById(string, Update) => Promise<Doc>
await db.updateById(string, Update) => Promise<Doc[]>
Find doc(s) matching query object. update
supports modifiers, but fields and modifiers cannot be mixed together. update
cannot create invalid field names, such as fields containing dots or fields starting with $
. Returns the updated docs.
If no modifiers are provided, update
will override the found doc(s) with update
_id
fields cannot be overwritten. Trying to do so will throw an error.
Example
// Data
// { _id: 1, type: 'normal', important: false, variants: ['weak', 'strong'] }
// { _id: 2, type: 'normal', important: true, variants: ['weak', 'strong'] }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
// Set all docs to {}
await db.updateMany()
// Set matching docs to { type: 'strong' }
// { _id: 1, type: 'strong' }
// { _id: 2, type: 'strong' }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
await db.updateMany({ type: 'normal' }, { type: 'strong' })
// _id fields will not be overwritten
// { _id: 1, type: 'strong' }
// { _id: 2, type: 'strong' }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
await db.updateMany({ type: 'normal' }, { type: 'strong', _id: 1 })
// Error, dot notation isn't a valid field
await db.updateOne({ type: 'normal' }, { 'properties.type': 'strong', _id: 1 })
Modifiers
Modifiers can be used to set specific values
$add
- Add value (number)$push
- Add value (array)$set
- Set value
Example
// Data
// { _id: 1 }
// { _id: 2 }
// { _id: 3, count: 3 }
// $add
// { _id: 3, count: 6 }
await db.updateMany({} }, { $add: { count: 3 } })
// { _id: 3, count: 3 }
await db.updateMany({}, { $add: { count: -3 } })
// $push
// { _id: 3, fruits: ['banana'] }
await db.updateMany({} }, { $push: { count: 'orange' } })
// { _id: 3 , fuits: ['banana', 'orange'] }
// $set
// { _id: 3, count: 'count' }
await db.updateMany({ $keys: ['count'] }, { $set: { count: 'count' } })
// { _id: 1, value: 3 }
// { _id: 2, value: 3 }
// { _id: 3, count: 3, value: 3 }
// Keys will be created if it does not exist
await db.updateMany({}, { $set: { value: 3 } })
Deleting docs
await db.deleteOne(Query) => Promise<Doc>
await db.deleteMany(Query) => Promise<number>
await db.deleteOneById(string) => Promise<Doc>
await db.deleteById(string| string[]) => Promise<number>
Delete doc(s) matching query object.
Example
// Data
// { _id: 1, type: 'normal', important: false, variants: ['weak', 'strong'] }
// { _id: 2, type: 'normal', important: true, variants: ['weak', 'strong'] }
// { _id: 3, type: 'strong', important: false, variants: ['weak', 'strong'] }
// { _id: 4, type: 'weak', variants: ['weak'], properties: { type: 'weak', parent: 3, variants: ['strong'] } }
// Delete all data
// [1, 2, 3, 4]
await db.deleteMany()
// Delete first match
// [2]
await db.delete({ _id: 2 })
// Delete all matches
// [3, 4]
await db.deleteMany({ type: 'normal' })
Drop
drop() => void
Clears both memory and database file.