A text file notes database


License
LGPL-3.0
Install
npm install notesdb@0.0.43

Documentation

notesdb Build Status tslint code style Test Runner NPM Coverage Status

A text file notes database

The text "database" is just a data structure (schema) and a set of functions to manipulate a structured directory. The "database" is referred to as a binder. The binder contains sections. Each of the sections contain notebooks. Each notebook contains artifacts (files). The general structure of the binder is:

  {binder}/
      {section}/
          {notebook 1}/
              - {artifact 1}
              - {artifact 2}
              - {artifact N}
          {notebook 2}/
              - {artifact 1}
              - {artifact 2}
              - {artifact N}
       {section N}/
           ...

The main component is the artifact. These are the text files. The other items in the structure above are basic file directory structures. Sections and notebooks are just organizing constructs (directories).

Features

  • Promised based public API
  • Automatic timed save of documents
  • Trashcan

Installation

This module uses yarn to manage dependencies and run scripts for development.

To install as an application dependency:

$ yarn add --dev notesdb

To build the app and run all tests:

$ yarn run all

Usage

BinderManager

This class is used to manage multiple Binder instances and their associated configurations. See the Binder api below for details.

The public api contains the following functions:

Creating a manager instance

import {BinderManager} from 'notesdb';

let manager = new BinderManager('/some/config/location');

This will create a new manager instance. It will also create a default binder within it if one does not exist. The default binder is stored in {HOME}/Notebooks/default.

Adding a new binder

import {BinderManager} from 'notesdb';

let manager = new BinderManager('/some/config/location');
manager.add('sampledb', '/directory/to/store/files');

This will add a new binder named sampledb if it doesn't exist. All data files created/saved by the binder will be stored in the directory /directory/to/store/files.

Remove a binder from the manager

import {BinderManager} from 'notesdb';

let manager = new BinderManager('/some/config/location');
manager.remove('sampledb');
manager.emptyTrash();

If the binder sampledb exists, then it will be moved to a trash directory. The location of the trash directory is determined by the manager instance at creation. In this example deleted binders would be stored in /some/config/location/binders/Trash. They are not removed from disk, but are basically stored to the trash directory. A call to emptyTrash() will permanently remove that binder's configuration details (but never the data files in the binder).

Binder

The public api contains the following functions:

Creating an Instance

To construct a new instance:

import {Binder} from 'notesdb';

let adb = new Binder();

Loads a default instance of the schema. This example contains no configuration options. It will use the default configuration directory of ~/.notesdb. The configuration file for this instance is saved in ~/.notesdb/config.json. The log file for the database is saved within this directory in ~/notesdb/notesdb.log. The default name of the database is adb (the name of the binder) and is saved in ~/.notesdb/adb. This is where all sections, notebooks, and artifacts will be saved.

An instance can be created with a new set of defaults:

import {Binder} from 'notesdb';

let adb = new Binder({
    binderName: 'sampledb',
    root: '~/mydb'
});

This will create a new binder with the name sampledb. It also changes where the configuration will be saved with the root option. This is the default location where the schema will be stored. If only the root is specified, then the configuration is also located in this directory. This database is saved in ~/mydb/sampledb. The configuration and the database can be separated:

import {Binder} from 'notesdb';

let adb = new Binder({
    binderName: 'sampledb',
    configRoot: '~/mydbconfig',
    root: '~/mydb'
});

This will create the database in ~/mydb/sampledb and the configuration/logs are stored in ~/mydbconfig.

Creating Sections

Once an instance is created sections can be added. There are two ways to do this. The create() or add. Using the create method:

import {Binder} from 'notesdb';

let adb = new Binder();
adb.create(['Test1', 'Test2'])
    .then((adb: Binder) => {
        // After the call to create the sections we list them after async creation
        let sections = adb.sections();
        sections.forEach((section: string) => {
            console.log(` ~> ${section}`);
        });

        return adb;
    })
    .catch((err: string) => {
        console.error(err);
    });

This will create two new sections within the schema named Test1 and Test2. It will also contain Default and Trash. Sections can also be created through the add method. This method uses an Artifact object during creation.

import {Artifact, Binder} from 'notesdb';

let adb = new Binder();
adb.add(Artifact.factory('fields', {section: 'Test1'}))
    .then((artifact: Artifact) => {
        console.log(artifact.toString());
        let sections = adb.sections();
        sections.forEach((section: string) => {
            console.log(` ~> ${section}`);
        });

        return adb;
    })
    .catch((err: string) => {
        console.error(err);
    });

This will create a new section named Test1 within a default notebook.

Creating Notebooks

A notebook is created with the add method.

import {Artifact, Binder} from 'notesdb';

let adb = new Binder();
adb.add(Artifact.factory('fields', {section: 'Test1', notebook: 'MyNotebook'}))
    .then((artifact: Artifact) => {
        console.log(artifact.toString());
        let notebooks = adb.notebooks();

        for (const {name, count} of notebooks) {
            console.log(` ~> ${notebook}: ${count}`);
        }

        return adb;
    })
    .catch((err: string) => {
        console.error(err);
    });

This will create a section named Test1 (if it doesn't exist) and a notebook within that section named MyNotebook.

Creating Artifacts

An artifact is the basic document within the notebook. These are always text files. That are created with the add method:

import {Artifact, Binder} from 'notesdb';

let adb = new Binder();
adb.add({
    section: 'Test1',
    notebook: 'MyNotebook',
    filename: 'myfile.txt'
    })
    .then((artifact: Artifact) => {
       console.log(artifact.toString());
    })
    .catch((err: string) => {
        console.error(err);
    });

Retrieving Artifacts

Artifacts are placed into the system by adding them (as above) or when an instance is created (the existing artifacts are loaded automatically). The artifacts are then retrieved from the system using the get method. When retrieving an artifact one must use section, notebook, and filename to retrieve it:

import {Artifact, Binder} from 'notesdb';

let adb = new Binder();
adb.get({
    section: 'Default',
    notebook: 'Default',
    filename: 'test1.txt'
    })
    .then((artifact: Artifact) => {
        // do something with the artifact
    })
    .catch((err: string) => {
        console.error(err);
    });

Finding Artifacts

One can also retrieve artifacts using the find method. This will perform a regex text search, using JavaScript regex, using all artifacts that are in the system. It will return an array of artifacts that contain the search string.

import {Artifact, Binder} from 'notesdb';

let adb = new Binder();
adb.find('#1')
    .then((artifacts: Array<Artifact>) => {
        artifacts.forEach((artifact: Artifact) => {
            console.log(artifact.absolute());
        });
    })
    .catch((err: string) => {
        console.error(err);
    });

This call would find all artifacts that contain the string #1 and return an array.

Renaming Artifacts

The name of an artifact can be changed using the rename method. It takes two parameters: the source location and the destination location:

import {Binder} from 'notesdb';

let adb = new Binder();
let src = {
    section: 'Test1',
    notebook: 'Default',
    filename: 'test4.txt'
}

let dst = {
    section: 'Test2',
    notebook: 'Default',
    filename: 'test4.txt'
}

adb.rename(src, dst)
    .then((filename: string) => {
        console.log(`renamed to: ${filename}`);
    })
    .catch((err: string) => {
        console.error(err);
    });

This example will rename the artifact Test1/Default/test4.txt to Test2/Default/test4.txt.

Trashing/Restoring Artifacts

Artifacts are not removed from the system directly (generally). They are first moved to a special Trash folder within the notebook. An artifact is removed with the trash method. A removed artifact can be recovered from the trash using the restore method:

import {Binder} from 'notesdb';

let adb = new Binder();
let lookup = {
    section: 'Test2',
    notebook: 'Default',
    filename: 'test4.txt'
}

adb.trash(lookup)
    .then((filename: string) => {
        console.log(`trashed: ${filename}`);
        return adb.restore(lookup);
    })
    .then((filename: string) => {
        console.log(`restored: ${filename}`);
    })
    .catch((err: string) => {
        console.error(err);
    });

The example above would move the artifact /Test2/Default/test4.txt and place it in the trash. After it is removed it is immediately restored back to its original path. If there is a name collision on delete/restore, then the current timestamp is placed on the artifacts name. In this example a file artifact is removed. Sections and notebooks can also be removed/restored.

Emptying the Trash

Artifacts that were removed are not permanently removed until the trash is emptied. That is performed with the emptyTrash method.

import {Binder} from 'notesdb';

let adb = new Binder();
adb.emptyTrash()
    .then((adb: Binder) => {
        assert(_.isEmpty(adb.schema.trash));
    })
    .catch((err: string) => {
        console.error(err);
    });

Shutdown

The shutdown method immediately saves the binder, marks the instance as uninitialized, and turns off the automatic saving of documents. It's a way to ensure proper cleanup once finished using this binder.

import {Binder} from 'notesdb';

let adb = new Binder();
adb.shutdown()
    .then((msg: string) => {
        console.log(msg);
    })
    .catch((err: string) => {
        console.error(err);
    });

It returns a message indicating that the binder was shutdown.

API

The API is generated with JSDoc. It can be found within the docs directory for the project. It can be generated with npm run docs.