smartdb
CouchDB library for Node.js with advanced entity and cache support
Features:
- Document <-> entity mappings - configure how to map your document to entities and back again
- Cache - get a performance boost by using the in-memory cache, Redis cache or your custom cache
- Validation - validate your entities before saving
- Multi-database support - use different databases for different entities transparently
- Unit test support - intelligent fake instance to use for your tests
Example
import SmartDb from 'smartdb';
let db = SmartDb({
databases: [
{
url: 'http://localhost:5984/userdb',
entities: {
user: { }
}
},
{
url: 'http://localhost:5984/blogdb',
entities: {
blogPost: { },
blogComment: { }
}
}
],
mapDocToEntity(doc) {
let type = doc.type;
if (type === 'user') return new User(doc);
if (type === 'blogPost') return new BlogPost(doc);
if (type === 'blogComment') return new BlogComment(doc);
throw new Error('Unsupported entity type: ' + type);
}
});
let johnDoe = new User({
fullName: 'John Doe',
email: 'john.doe@mail.com'
});
await db.save(johnDoe);
let blogPost = await db.get('blogPost', blogPostId);
API
db.get(type, id)
Get entity by type and ID. Returns promise to entity. If no document found, will return an error.
db.getOrNull(type, id)
Same as db.get() but return null instead of error when no document found.
Will also return null if id
is null/undefined, which can be useful in some situations to keep code compact.
db.save(entity)
Saves an unsaved entity. Returns promise. The properties _id and _rev will automatically be set on the
given entity after save complete.
db.update(entity)
Updates an existing entity. Returns promise. Must have _id and _rev defined. Will automatically set _rev on
the given entity after update complete.
db.merge(type, id, changedProperties)
Change specific properties on an entity.
let { rev } = await db.merge('user', userId, { email: 'a.new@email.com' });
db.remove(type, id)
Removes an entity by type and ID. Returns promise
db.view(type, viewName, args)
Calls a view and returns entities based on the documents in the response.
Returns promise of entities.
Will by default use a design document with the same name as type
. However, this is configurable by using the rewriteView
option.
You do not need to pass include_docs: true
to the args, it is automatically set.
let users = await db.view('user', 'byDepartment', { key: '<DEPT_ID>' });
db.viewRaw(type, viewName, viewArgs)
Calls a view and returns a promise with the raw JSON rows from CouchDB.
Useful when you want to use the key and value properties.
Will by default use a design document with the same name as type
. However, this is configurable by using the rewriteView
option.
db.list(type, listName, viewName, args)
Calls a list function and returns the raw result from CouchDB. Returns promise of body.
Options
These are the options you can give when creating the smartdb instance:
databases
An array of databases where you define where entities are located.
You can also set cache settings per entity.
Define one array item for each database where you have entites.
In the database url
you set the full path to the database. That includes protocol (http or https),
optional authentication, hostname and database name.
{
databases: [
{
url: 'http://username:password@somehost:5984/blog',
entities: {
blogPost: {
cacheMaxAge: 5 * 60 * 1000,
cacheMaxSize: 100
},
blogComment: { }
}
}
]
}
typeProperty
The property on the entity that identies the entity type. Default is 'type'
.
{
type: 'entityType'
}
mapDocToEntity
Maps a document to an entity. This is useful if you want to wrap the document and add methods to interact with the data.
The default is to just returns the JSON document retrieved from the database.
{
mapDocToEntity(doc) {
let map = {
user: User,
blogPost: BlogPost,
blogComment: BlogComment
};
let Constructor = map[doc.type];
return new Constructor(doc);
}
}
mapEntityToDoc
Maps an entity to the document to save to the database. The default is to just use JSON.stringify() on the entity.
In some cases you might want to strip it of some properties or change something before saving, then define a function here.
One way might be to have a convention to have a toDoc()
method on entities.
{
mapEntityToDoc(entity) {
if (entity.toDoc) {
return entity.toDoc();
}
return entity;
}
}
cacheProvider
By default, smartdb uses an in-memory cache inside the same Node.js process.
This works well when you only have a single Node.js process that use your CouchDB database.
If you have multiple Node.js processes the recommendation is to use the
Redis cache provider that is available for smartdb.
import RedisCacheProvider from 'smartdb-rediscacheprovider'
{
cacheProvider: RedisCacheProvider({ })
}
validate
You might want to validate your entities before sending them to CouchDB for saving. The signature of this function is
(entity, callback)
. If you return an error in the callback smartdb will not send the request to CouchDB but
instead return an error.
{
validate(entity, callback) {
if (entity.validate) {
entity.validate(callback);
}
else {
callback();
}
}
}
rewriteView
The default when using db.view('user', 'byDepartment', ...)
is to use the view byDepartment
in the design document user
. But you might want a different strategy. If you use this option,
define a function that given (type,viewName)
should return an array
with the following format [designDocumentName, viewName]
Personally, I use a single view per design document to be able to add a view without causing re-indexing of many
other views.
{
rewriteView(type, viewName) {
return [type + '-' + viewName, 'fn'];
}
}
This means that db.view('user', 'byDepartment', ...)
would go to the
design document user-byDepartment
and the view named fn
.
Errors
For get
and merge
if entity does not exist, gives the following error:
{
name: 'EntityMissingError',
message: 'Entity is missing',
scope: 'smartdb',
entityId: 'XYZ123',
entityType: 'user'
}
For save
, merge
, remove
and update
if you get a conflict, gives the following error:
{
name: 'EntityConflictError',
message: 'Conflict when trying to persist entity change',
scope: 'smartdb',
entityId: 'XYZ123',
entityType: 'user'
request: {
method: 'PUT',
headers: { ... },
uri: 'http://...',
body: '{ ... }'
},
response: {
statusCode: 409,
headers: { ... }
}
}
License
(The MIT License)
Copyright (c) 2013-2022 Calle Arnesten
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.