node package manager

nor-nopg

NoSQL Library for PostgreSQL

nor-nopg

This is a Node.js library which implements NoSQL features over a PostgreSQL v9.3 database.

var nopg = require('nor-nopg');

Before using the database you should initialize it by calling: nor-nopg --pg=psql://localhost:5432/test init

  • nor-app -- provides a HTTP interface in to a nor-nopg database
  • nopg -- Shell scripting CLI for nor-nopg
Name Description Example
NoPg NoPg module var NoPg = require('nor-nopg');
db NoPg instance NoPg.start(...).then(function(db) { ... });
doc NoPg.Document instance db.create()(...).shift().then(function(doc) { ... });
type NoPg.Type instance db.declareType("name")().shift().then(function(type) { ... });
Short usage Description Tested at
NoPg.start(...) Get connection and start transaction L42
db.init() Initialize database L15
db.create()({"hello":"world"}) Create document without type L41
db.create("MyType")({"hello":"world"}) Create document with type as string L57
db.create(type)({"hello":"world"}) Create document with type as object L306
db.search()({"$id": "b58e402e-6b39-11e3-99c7-0800279ca880"}) Search documents by id L156
db.search()({"hello": "world"}) Search documents by values L130
db.search()(function(doc) { return doc.hello === 'world'; }) Search documents by custom function
db.search("Foobar")() Search documents by type string L185
db.search("Foobar")({"name":"hello"}) Search documents by type string with values L219
db.search(type)() Search documents by type
db.search(type)({"name":"hello"}) Search documents by type as string with values L254
db.update(doc) Edit document by instance of NoPg.Document L93
db.update(doc, {"hello": "world"}) Edit document by plain document L74
n/a Edit documents by type
db.del(doc) Delete document by instance of NoPg.Document L113
n/a Delete documents by instance of NoPg.Type
db.del(type) Delete type by instance of NoPg.Type L400
db.del(attachment) Delete attachment
db.declareType("Product")({"$schema":{"type":"object"}}) Create or replace type with name as string
db.createType("Product")({"$schema":{"type":"object"}}) Create type with name as string
db.createType()({"$schema":{"type":"object"}}) Create type without name
db.update(type) Edit type by instance of NoPg.Type Yes
db.update(type, {$schema:{...}}) Edit type by plain object Yes
db.searchTypes({"$id": "b58e402e-6b39-11e3-99c7-0800279ca880"}) Search types
doc.createAttachment(data, {"content-type": "image/png"}) Create attachments
doc.searchAttachments() Search attachments
doc.getAttachment("b58e402e-6b39-11e3-99c7-0800279ca880") Search attachments
db.import('/path/to/tv4.js', {'$name': 'tv4'}) Import or upgrade module in database
PostgreSQL JavaScript Description
id obj.$id Property with leading $ is mapped to the actual database table column
content->>'name' or content->'name' obj.name Property without leading $ is mapped to the property of the primary JSON data variable. It's content for NoPg.Documents and meta for other objects. The string or number operator is detected automatically from the type of the value.
nopg.start('postgres://user:pass@localhost/dbname').then(function(db) {
    /* ... */
    return db.commit();
});

You must call

  • db.commit() to actually save any changes to the database; or
  • db.rollback() to cancel the transaction

The required table structures and initial settings and data can be created or upgraded by calling db.init():

nopg.start(PGCONFIG).init().then(function(db) {
    ...
});

NoPG (from v1.1.0) has integrated support for events using PostgreSQL's tcn extension.

Please note: You need to use .connect() instead of .start(...), since listening in a transaction will block other threads!

  • Listener gets arguments:
    • id - UUID of the document which has been created
    • eventName - Name of the event
    • type - Type name
nopg.connect(PGCONFIG).then(function(db) {
    return db.on('User#create', function db_on_create(id) {
        // User with UUID as `id` was created 
    });
});
  • Listener gets arguments:
    • id - UUID of the document which has been created
    • eventName - Name of the event
    • type - Type name
nopg.connect(PGCONFIG).then(function(db) {
    return db.on('User#create', function db_on_create(id) {
        // User with UUID as `id` was created 
        db.on('User#'+id+'@', function db_on_create(id, eventName) {
            // User with UUID as `id` was updated or deleted 
        }).fail(...).done();
    });
});
  • Listener gets arguments:
    • id - UUID of the document which has been changed
    • eventName - Name of the event
    • type - Type name
  • Listener gets arguments:
    • id - UUID of the type which has been deleted
    • eventName - Name of the event
    • type - Type name
  • Listener gets arguments:
    • id - UUID of the document which has been created
    • eventName - Name of the event
nopg.connect(PGCONFIG).then(function(db) {
    return db.on('create', function db_on_create(id) {
        // Document with UUID as `id` was created 
    });
});
  • Listener gets arguments:
    • id - UUID of the document which has been changed
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the document which has been deleted
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the type which has been created
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the type which has been changed
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the type which has been deleted
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the attachment which has been created
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the attachment which has been changed
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the attachment which has been deleted
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the lib which has been created
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the lib which has been changed
    • eventName - Name of the event
  • Listener gets arguments:
    • id - UUID of the lib which has been deleted
    • eventName - Name of the event

We also implement few events which are not through PostgreSQL. These will happen only on the local instance.

When connection will be disconnected.

When transaction has been commited.

When transaction has been rollback'd.

If you want your application to use custom LISTEN or NOTIFY, we recommend directly using nor-pg. It has a nice familiar interface for it.

There is no reason to implement this feature in NoPG since implementing it wouldn't invent anything new; it would be direct wrapper for nor-pg.

We use an extended promise implementation which allows chaining of multiple methods together.

Under the hood we are using q promises which are extended using nor-extend.

However these extended features are not required. You may use our promises just like any other q-promises.

NoPg.start(...).create("Group")({"name":"Bar"}).create("User")({"name":"Foo"}).then(function(db) {
    var group = db.fetch();
    var user = db.fetch();
 
    // ... do your magic at this point 
 
    return db.commit();
}).fail(function(err) {
    console.error(err);
}).done();

The module has simple ORM mappings for all of our PostgreSQL tables.

JavaScript constructor PostgreSQL table Default JSON column
NoPg.Document documents content
NoPg.Type types meta
NoPg.Attachment attachments meta
NoPg.Lib libs meta
NoPg.Method methods meta
NoPg.DBVersion dbversions n/a

These constructors will take an object and convert it to JavaScript instance of that PostgreSQL table row.

Example object:

{
    "name": "Hello",
    "foo": "bar",
    "age": 10
    "$id": "8a567836-72be-11e3-be5d-0800279ca880",
    "$created": "",
    "$updated": ""
}

The special $ in the name makes it possible to point directly to a column in PostgreSQL row.

Any other property points to the column in default JSON column.

For example a obj.$meta.foo in NoPg.Type instance has the same value as obj.foo unless the ORM instance has been changed by the user.

db.create()({"hello":"world"}).then(function(db) {
    var doc = db.fetch();
    console.log("Successfully created new document: " + util.inspect(doc) );
});
db.create("MyType")({"hello":"world"}).then(function(db) {
    var doc = db.fetch();
    console.log("Successfully created new document: " + util.inspect(doc) );
});

Tested at test-nopg.js:57.

db.create(type)({"hello":"world"}).then(function(db) {
    var doc = db.fetch();
    console.log("Successfully created new document: " + util.inspect(doc) );
});
db.search()({"$id": "b58e402e-6b39-11e3-99c7-0800279ca880"}).then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
db.search()({"hello": "world"}).then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
db.search()(function(doc) {
    return doc.hello === 'world';
}).then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
db.search("Foobar")().then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
db.search("Foobar")({"name":"hello"}).then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
db.search(type)().then(function(db) {
    var list = db.fetch();
    console.log("Found documents: " + util.inspect(list) );
});
doc.hello = "world";
 
db.update(doc).then(function(db) {
    console.log("Successfully edited document: " + util.inspect(doc) );
});

Tested at test-nopg.js:93.

db.update(doc, {"hello": "world"}).then(function(db) {
    console.log("Successfully edited document: " + util.inspect(doc) );
});

Tested at test-nopg.js:74.

/* n/a */
db.del(doc).then(function(db) {
    console.log("Document deleted succesfully.");
});

Tested at test-nopg.js:113.

// n/a 
db.del(type).then(function(db) {
    console.log("Type deleted succesfully.");
});
db.del(attachment).then(function(db) {
    console.log("Attachment deleted succesfully.");
});
db.declareType("Product")({"schema":{"type":"object"}}).then(function(db) {
    var type = db.fetch();
    console.log("Successfully fetched a type: " + util.inspect(type) );
});
db.createType("Product")({"schema":{"type":"object"}}).then(function(db) {
    var type = db.fetch();
    console.log("Successfully created new type: " + util.inspect(type) );
});
db.createType()({"schema":{"type":"object"}}).then(function(db) {
    var product_type = db.fetch();
    console.log("Successfully created new type: " + util.inspect(product_type) );
});
type.schema = {..};
db.update(type).then(function(db) {
    console.log("Successfully edited type: " + util.inspect(type) );
});
db.update(type, {schema:{...}}).then(function(db) {
    console.log("Successfully edited type: " + util.inspect(type) );
});
db.searchTypes({"$id": "b58e402e-6b39-11e3-99c7-0800279ca880"}).then(function(db) {
    var list = db.fetch();
    console.log("Found types: " + util.inspect(list) );
});
nopg.createAttachment(doc)(file, {"content-type": "image/png"}).then(function(db) {
    var file = db.fetch();
    console.log("Successfully created new attachment: " + util.inspect(file) );
});
  • If doc is undefined then document is looked from previous value in the buffer which must by nopg.Attachment or nopg.Document.
doc.searchAttachments(doc)(opts).then(function(db) {
    var list = db.fetch();
    console.log("Found attachments: " + util.inspect(list) );
});
  • If you omit doc, the last element in the queue will be used.
  • If you omit opts, then all attachments are listed otherwise only matching.
doc.getAttachment("b58e402e-6b39-11e3-99c7-0800279ca880").then(function(db) {
    var attachment = db.fetch();
    console.log("Found attachment: " + util.inspect(attachment) );
});
db.import('/path/to/tv4.js', {'name': 'tv4'}).then(function(db) {
    console.log("Library imported succesfully.");
});

Database configurations can be set using PGCONFIG:

export PGCONFIG='pg://user:password@localhost/db'

The actual test can be run: npm test

You must delete the data if you need to run the test suite again for the same database:

psql -q db < scripts/cleanup.sql

Please note: psql does not follow PGCONFIG environment variable!

npm run lint

You can buy commercial support from Sendanor.

We use Trello board to organize development and keep track on things to do.

Name Type
version integer
modified timestamptz
Name Type
id uuid
name text
schema json
validator text
meta json
created timestamptz
modified timestamptz
Name Type
id uuid
types_id uuid
type text
name text
body text
meta json
active boolean
created timestamptz
modified timestamptz
Name Type
id uuid
content json
types_id uuid
created timestamptz
modified timestamptz
type text
Name Type
id uuid
documents_id uuid
content bytea
meta json
created timestamptz
modified timestamptz
Name Type
id uuid
name text
content text
meta json
created timestamptz
modified timestamptz

This database structure has not been updated.