1.3.2 • Published 1 year ago

beastdb v1.3.2

Weekly downloads
-
License
ISC
Repository
-
Last release
1 year ago

beastDB

A persistence database specializing in state space search problems!

Overview

BeastDB purpose is to help model, consult and persiste states in a eficience way, it does this by using leveldb as storage and introducing two immutable data types ISet and IMap.

ISet and IMap are implemented using immutable structural sharing, making copy and change operations very fast and memory eficient and strutural sharing representation is preserved on storage, making it very fast to load and save states.

This is the main diference bettwen BeastDB and other databases, but its not the only one:

  • Create Database
  • Tables, key and indexes
  • Records
  • Types
  • Query

Install

    npm install beastdb --save

Use

Just require the beastdb module.

    const {DB} = require("beastdb");

Create Database

To create a database just pass the storage option with a path.

  const db = await DB.open({storage: {path: './tttdb'}});

Tables, key and indexes

Tables are created on the fly like this:

  const t = db.tables.tictactoe;

Key and indexes are optional, but they need to created on start.

  const t = await db.tables.tictactoe
    .key('stateID', ['game', 'turn', 'moves'])
    .index('state')
    .index('win', 'state')
    .index('game')
    .save();

After creating key and index we must call save to storage changes.

key

There is only one key per table, on a SQL database this would be a primary key. A key is unique and immutable, when creating a compound key all fields in the key are not allowed to change.

If a key is not provided then table would use id has primary key, and uuid is used as a key generator.

If a key is provided with no fields then key name is used as primary key and uuid is used as a key generator, ex.

    t.key('stateID')

Indexes

Indexes can have one or more fields, currently you can't make queries without them, ex.

  t.index('state')
  .index('win', 'state')

This allows us to query 'state' alone and 'win' and 'state' togheter.

findByIndex(searchObject)

It finds a record using a index or key, the index must be declared, if the search uses more then one field then there must exist an index with all the search fields,

ex. t.index('win', 'state'); db.tables.games.findByIndex({win: 'O', state: 'done');

You can make indexes and search for any field type, for example IMap:

const gameState = await db.iMap().chain
        .set(0, await db.iMap().chain.set(0, 'X').set(1, '#').set(2, 'O'))
        .set(1, await db.iMap().chain.set(0, '#').set(1, 'O').set(2, 'O'))
        .set(2, await db.iMap().chain.set(0, 'X').set(1, '#').set(2, 'X'))
;
    
for await (let state of t.findByIndex({game: gameState})) {
        const childs = await state.data.childs;

        for (let child of childs) {
            print(child);
        }
}

Records

To create and update records we use insert and update operations. There is no restriction to add fields on insert or update.

Insert

Insert takes two arguments the record and on conflict strategy:

    const record = await t.insert({
        moves: 0,
        state: 'expand',
        turn: '',
        childs: [],
        game: await db.iMap().chain
            .set(0, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
            .set(1, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
            .set(2, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
    }, null);
  • null : ignore if alredy exists,
  • object: on conflict update with object data, ex:
    const record = await t.insert({
        moves: 0,
        state: 'expand',
        turn: '',
        childs: [],
        game: await db.iMap().chain
            .set(0, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
            .set(1, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
            .set(2, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
    }, {state: 'existing'});
  • async function (oldData, newData) {t.update(...)} : a function to solve the conflict

Update

After a record is created it can be updated like this:

    await record.update({
       state: 'done', myNewField: 'do it'
    });

Updates can't contain any field of primary key.

Types

Records only suport this types of values,

Record

A record can have other records as value, this can be from the same table or from another table, ex:

    await record.update({
       parent: await db.tables.myOtherTable.insert({'doit': 'now'})
    });

It can even be recursive:

    await record.update({
       self: record
    });

Date, Map, Set

Javascript Date, Map and Set is accepted.

Map and Set can have any value of the types described in this section, ex.

    await record.insert({
       map: new Set([new Date(), record, 1, 'string', ...])
    });

JSON

Any json value like number, string, object, array ...

Objects and Arrays can be nested and can have any type of value described on this section, ex:

    await record.insert({
       o: {
          date: new Date(),
          records: [record1, record2],
          mySet: new Set([1, 2, 3])
       }    
    });

IMap and ISet

IMap and ISet are immutable so every write operation will return a new IMap or ISet.

IMap

Since IMap is created on every write operation we can start with an empty imap and then add new labels like this:

    const empty = await db.iMap();
    let s1 = await empty.set('label1', value);
    s1 = await s1.set('label2', value);    

After completing our imap we can save it to a record, this will only save the last IMap and discard all intermidiate states.

    await record.insert({
       mymap: s1    
    });

chain

The IMap chain fields allows to chain async methods like this:
    await db.iMap().chain
        .set(0, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
        .set(1, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
        .set(2, await db.iMap().chain.set(0, '#').set(1, '#').set(2, '#'))
Withoud the chain field the sets would have to be used sequentialy, like this.
    let a = await db.iMap().set(0, 'example 0');
    a = await a.set(1, 'example 1');
    a = await a.set(2, 'example 2');

async set(key, value)

It sets a key value pair to the iMap
    let a = await db.iMap().set(0, 'example 0');
    a = await a.set(1, 'example 1');
    a = await a.set(2, 'example 2');

Notes:

  • All keys on IMap are strings, all non string keys will be converted to string
  • Values can be any type described on the #Types sections.

async get(key)

get(key) return the value of the given key

async has(key)

has(key) checks if IMap has the given key.

async remove(key)

It removes a key from a IMap
    let a = await db.iMap().set(0, 'example 0');
    a = await a.set(1, 'example 1');
    a = await a.set(2, 'example 2');

    a.remove(1);

async iterators

It creates a iterator to iteratate [key, value] from IMap.
    const r = [];
    for await (let [key, value] of myIMap) {
        r.push([key, value]);
    }

    return r;

async *values()

creates a new iterator to iterate all map values.
    const r = [];
    for await (let value of myIMap.values()) {
        r.push(value);
    }

    return r;

async *keys()

creates a new iterator to iterate all map keys.
    const r = [];
    for await (let key of myIMap.keys()) {
        r.push(key);
    }

    return r;

async keysToArray()

It returns an array with all map keys.

async toArray()

It returns a json representation of IMap, the format is the same as norml JS Map.

example: [['keyA', 'valueA'], ['keyB', 'valueB'], ['keyC', 'valueC']]

ISet

Since ISet is created on every write operation we can start with an empty iset and then add new values like this:

    const empty = await db.iSet();
    let s1 = await empty.add(value1);
    s1 = await s1.add(value2);    

After completing our iset we can save it to a record, this will only save the last ISet and discard all intermidiate states.

    await record.insert({
       myset: s1
    });

chain

The ISet chain fields allows to chain async methods like this:
    await db.iSet().chain.add(1).add(2).add(3)
Withoud the chain field the adds would have to be used sequentialy, like this.
    let a = await db.iSet().add(1);
    a = await a.add(2);
    a = await a.add(3);

async add(value)

Adds a value to ISet

Notes: * Values can be any type described on the #Types sections.

async remove(value)

Removes a value from the ISet.

async iterators

It creates a iterator to iteratate [key, value] from ISet.
    const r = [];
    for await (let [key, value] of myISet) {
        r.push([key, value]);
    }

    return r;

In this case the keys would be 0, 1, ...

async *values()

creates a new iterator to iterate all ISet values.
    const r = [];
    for await (let value of myISet.values()) {
        r.push(value);
    }

    return r;

async toArray()

It returns an array with all ISet values,

example: 1, 2, 3, 4

IArray

Since IArray is created on every write operation we can start with an empty iarray and then add new values like this:

    const empty = await db.iArray();
    let s1 = await empty.push(value1);
    s1 = await s1.push(value2);    

After completing our iarray we can save it to a record, this will only save the last IArray and discard all intermidiate states.

    await record.insert({
       myarray: s1
    });

chain

The IArray chain fields allows to chain async methods like this:
    await db.iArray().chain.push(1).push(2).push(3)
Withoud the chain field the adds would have to be used sequentialy, like this.
    let a = await db.iArray().push(1);
    a = await a.push(2);
    a = await a.push(3);

async push(value)

Pushes a value to the last position of the iarray and returns a new iarray.

    let a = await db.iArray().push(1);
    a = await a.push(2);
    a = await a.push(3);

async pop()

Pops the last value of array, it returns the new iarray and value.

    let [newIArray, popedValue] = await db.iArray().chain.push(1).pop();

async setIndex(index, value)

length

The size of the array

  const size = iarray.length;

setIndex(index, value)

Sets the index value to value, index must be inside of interval 0, this.size

  let newIarray = await iarray.chain.push(1).push(2).push(3).setIndex(1, 2);

async toArray()

It returns an array with all IArray values in inserted order,

example: 1, 2, 3, 4

Example

The Name

The name beast derives from brute-force and was chosen as a synonym of brute.

1.2.0

1 year ago

1.1.0

1 year ago

1.3.2

1 year ago

1.3.1

1 year ago

1.3.0

1 year ago

1.0.0

2 years ago