RavenDB Client for Node.js

Installation

npm install --save ravendb

Releases and Changelog - click here

Documentation

Please find the official documentation on RavenDB Documentation page.

Getting started

Require DocumentStore class from package

const { DocumentStore } = require ( ' ravendb ' ) ;

or (using ES6 / Typescript imports)

import { DocumentStore } from ' ravendb ' ;

Initialize document store (you should have one DocumentStore instance per application)

const store = new DocumentStore ( ' http://live-test.ravendb.net ' , ' databaseName ' ) ; store . initialize ( ) ;

Open a session

const session = store . openSession ( ) ;

Call saveChanges() once you're done:

session . load ( ' users/1-A ' ) . then ( ( user ) => { user . password = PBKDF2 ( ' new password ' ) ; } ) . then ( ( ) => session . saveChanges ( ) ) . then ( ( ) => { } ) ;

Supported asynchronous call types

async / await

const session = store . openSession ( ) ; let user = await store . load ( ' users/1-A ' ) ; user . password = PBKDF2 ( ' new password ' ) ; await session . saveChanges ( ) ;

Promises

session . load ( ' Users/1-A ' ) . then ( ( user ) => { user . password = PBKDF2 ( ' new password ' ) ; } ) . then ( ( ) => session . saveChanges ( ) ) . then ( ( ) => { } ) ;

Callbacks

session . load ( ' users/1-A ' , ( user ) => { user . password = PBKDF2 ( ' new password ' ) ; session . saveChanges ( ( ) => { } ) ; } ) ;

CRUD example

Storing documents

let product = { title : ' iPhone X ' , price : 999 . 99 , currency : ' USD ' , storage : 64 , manufacturer : ' Apple ' , in_stock : true , last_update : new Date ( ' 2017-10-01T00:00:00 ' ) } ; await session . store ( product , ' products/ ' ) ; console . log ( product . id ) ; await session . saveChanges ( ) ;

Loading documents

const product = await session . load ( ' products/1-A ' ) ; console . log ( product . title ) ; console . log ( product . id ) ;

Loading documents with includes

const session = store . openSession ( ) ; const user1 = await session . include ( " kids " ) . load ( " users/1 " ) ; const user2 = await session . load ( " users/2 " ) ; assert . ok ( user1 ) ; assert . ok ( user2 ) ; assert . equal ( session . advanced . numberOfRequests , 1 ) ;

Updating documents

let product = await session . load ( ' products/1-A ' ) ; product . in_stock = false ; product . last_update = new Date ( ) ; await session . saveChanges ( ) ; product = await session . load ( ' products/1-A ' ) ; console . log ( product . in_stock ) ; console . log ( product . last_update ) ;

Deleting documents

Using entity

let product = await session . load ( ' products/1-A ' ) ; await session . delete ( product ) ; await session . saveChanges ( ) ; product = await session . load ( ' products/1-A ' ) ; console . log ( product ) ;

Using document ID

await session . delete ( ' products/1-A ' ) ;

Querying documents

Use query() session method:

By collection:

const query = session . query ( { collection : ' products ' } ) ;

By index name:

const query = session . query ( { indexName : ' productsByCategory ' } ) ;

Using entity type:

import { User } from " ./models " ; const query = session . query ( User ) ;

Build up the query - apply conditions, set ordering etc. Query supports chaining calls:

query . waitForNonStaleResults ( ) . usingDefaultOperator ( ' AND ' ) . whereEquals ( ' manufacturer ' , ' Apple ' ) . whereEquals ( ' in_stock ' , true ) . whereBetween ( ' last_update ' , new Date ( ' 2017-10-01T00:00:00 ' ) , new Date ( ) ) . orderBy ( ' price ' ) ;

Finally, you may get query results:

const results = await query . all ( ) ; const firstOne = await query . first ( ) ; const single = await query . single ( ) ;

DocumentQuery methods overview

selectFields() - projections using a single field

const userNames = await session . query ( { collection : " users " } ) . selectFields ( " name " ) . all ( ) ;

selectFields() - projections using multiple fields

await session . query ( { collection : " users " } ) . selectFields ( [ " name " , " age " ] ) . all ( ) ;

await session . query ( { collection : " users " } ) . selectFields ( " age " ) . distinct ( ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereEquals ( " age " , 30 ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereIn ( " name " , [ " John " , " Thomas " ] ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereStartsWith ( " name " , " J " ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereBetween ( " registeredAt " , new Date ( 2016 , 0 , 1 ) , new Date ( 2017 , 0 , 1 ) ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereGreaterThan ( " age " , 29 ) ; . all ( ) ;

Checks if the field exists.

await session . query ( { collection : " users " } ) . whereExists ( " kids " ) ; . all ( ) ;

await session . query ( { collection : " users " } ) . containsAll ( " kids " , [ " Mara " , " Dmitri " ] ) ; . all ( ) ;

Performs full-text search.

await session . query ( { collection : " users " } ) . search ( " kids " , " Mara Dmitri " ) ; . all ( ) ;

await session . query ( { collection : " users " } ) . whereExists ( " kids " ) . orElse ( ) . openSubclause ( ) . whereEquals ( " age " , 25 ) . whereNotEquals ( " name " , " Thomas " ) . closeSubclause ( ) ; . all ( ) ;

await session . query ( { collection : " users " } ) . not ( ) . whereEquals ( " age " , 25 ) . all ( ) ;

await session . query ( { collection : " users " } ) . whereExists ( " kids " ) . orElse ( ) . whereLessThan ( " age " , 30 ) . all ( ) ;

Sets default operator (which will be used if no andAlso() / orElse() was called. Just after query instantiation, OR is used as default operator. Default operator can be changed only adding any conditions.

await session . query ( { collection : " users " } ) . orderBy ( " age " ) . all ( ) ;

Limits the number of result entries to count .

await session . query ( { collection : " users " } ) . orderBy ( " age " ) . take ( 2 ) . all ( ) ;

Skips first count results.

await session . query ( { collection : " users " } ) . orderBy ( " age " ) . take ( 1 ) . skip ( 1 ) . all ( ) ;

Getting query statistics

To obtain query statistics use statistics() method.

let stats : QueryStatistics ; const results = await session . query ( { collection : " users " } ) . whereGreaterThan ( " age " , 29 ) . statistics ( s => stats = s ) . all ( ) ;

all() - returns all results

first() - first result

single() - first result, throws error if there's more entries

count() - returns the count of the results (not affected by take() )

Attachments

Store attachments

const doc = new User ( { name : " John " } ) ; await session . store ( doc ) ; const fileStream = fs . createReadStream ( " ../photo.png " ) ) ; session . advanced . attachments . store ( doc , " photo.png " , fileStream , " image/png " ) ; session . advanced . attachments . store ( doc . id , " photo.png " , fileStream , " image/png " ) ; await session . saveChanges ( ) ;

Get attachments

const attachment = await session . advanced . attachments . get ( documentId , " photo.png " ) attachment . data . pipe ( fs . createWriteStream ( " photo.png " ) ) . on ( " finish " , ( ) => next ( ) ) ;

Check if attachment exists

await session . advanced . attachments . exists ( doc . id , " photo.png " ) ) ; await session . advanced . attachments . exists ( doc . id , " not_there.avi " ) ) ;

Get attachment names

await session . advanced . attachments . getNames ( doc ) ;

Bulk Insert

const bulkInsert = store . bulkInsert ( ) ; for ( const name of [ " Anna " , " Maria " , " Miguel " , " Emanuel " , " Dayanara " , " Aleida " ] ) { const user = new User ( { name } ) ; await bulkInsert . store ( user ) ; } await bulkInsert . finish ( ) ;

Changes API

Listen for database changes e.g. document changes.

const changes = store . changes ( ) ; const docsChanges = changes . forAllDocuments ( ) ; docsChanges . on ( " data " , change => { } ) ; docsChanges . on ( " error " , err => { } ) { const session = store . openSession ( ) ; await session . store ( new User ( { name : " Starlord " } ) ) ; await session . saveChanges ( ) ; } changes . dispose ( ) ;

Streaming

Stream documents with ID prefix

const userStream = await session . advanced . stream ( " users/ " ) ; userStream . on ( " data " , user => { } ) ; userStream . on ( " error " , err => { } )

Stream query results

const query = session . query ( { collection : " users " } ) . whereGreaterThan ( " age " , 29 ) ; let stats ; const queryStream = await session . advanced . stream ( query , _ => stats = _ ) ; queryStream . on ( " data " , user => { } ) ; queryStream . once ( " stats " , stats => { } ) ; queryStream . on ( " error " , err => { } ) ;

Revisions

NOTE: Please make sure revisions are enabled before trying one of the below.

const session = store . openSession ( ) ; const user = { name : " Marcin " , age : 30 , pet : " users/4 " } ; await session . store ( user , " users/1 " ) ; await session . saveChanges ( ) ; user . name = " Roman " ; user . age = 40 ; await session . saveChanges ( ) ; const revisions = await session . advanced . revisions . getFor ( " users/1 " ) ;

Suggestions

class UsersIndex extends AbstractIndexCreationTask { constructor ( ) { super ( ) ; this . map = " from doc in docs.Users select new { doc.name } " ; this . suggestion ( " name " ) ; } } const session = store . openSession ( ) ; const suggestionQueryResult = await session . query ( { collection : " users " } ) . suggestUsing ( x => x . byField ( " name " , " Jon " ) ) . execute ( ) ;

Advanced patching

session . advanced . increment ( " users/1 " , " age " , 1 ) ; session . advanced . patch ( " users/1 " , " underAge " , false ) ; await session . saveChanges ( ) ;

Subscriptions

const subscriptionName = await store . subscriptions . create ( { query : " from users where age >= 30 " } ) ; const subscription = store . subscriptions . getSubscriptionWorker ( { subscriptionName } ) ; subscription . on ( " error " , err => { } ) ; subscription . on ( " batch " , ( batch , callback ) => { try { callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) ;

Using object literals for entities

In order to comfortably use object literals as entities set the function getting collection name based on the content of the object - store.conventions.findCollectionNameForObjectLiteral() .

const store = new DocumentStore ( urls , database ) ; store . conventions . findCollectionNameForObjectLiteral = entity => entity [ " collection " ] ; store . initialize ( ) ;

This needs to be done before an initialize() call on DocumentStore instance. If you fail to do so, your entites will land up in @empty collection having an UUID for an ID. E.g.

Using classes for entities

Define your model as class. Attributes should be just public properties:

export class Product { constructor ( id = null , title = ' ' , price = 0 , currency = ' USD ' , storage = 0 , manufacturer = ' ' , in_stock = false , last_update = null ) { Object . assign ( this , { title , price , currency , storage , manufacturer , in_stock , last_update : last_update || new Date ( ) } ) ; } }

To store a document pass its instance to store() . Collection name will be detected automatically using entity's class name.

import { Product } from " ./models " ; let product = new Product ( null , ' iPhone X ' , 999 . 99 , ' USD ' , 64 , ' Apple ' , true , new Date ( ' 2017-10-01T00:00:00 ' ) ) ; product = await session . store ( product ) ; console . log ( product instanceof Product ) ; console . log ( product . id . includes ( ' products/ ' ) ) ; await session . saveChanges ( ) ;

When loading document, you can use session.load() . Pass class constructor as a second argument:

let product = await session . load ( ' products/1-A ' , Product ) ; console . log ( product instanceof Product ) ; console . log ( product . id ) ;

NOTE: To limit passing class constructors around, register the type in document store's conventions like so:

import { Product } from " ./models " ; const store = new DocumentStore ( url , dbName ) ; store . conventions . registerEntityType ( Product ) ; let product = await session . load ( ' products/1-A ' ) ; console . log ( product instanceof Product ) ; console . log ( product . id ) ;

When querying documents, you pass class constructor as documentType option of session.query({ ... }) :

let products = await session . query ( { collection : ' products ' , documentType : Product } ) . all ( ) ; products . forEach ( ( product ) => { console . log ( product instanceof Product ) ; console . log ( product . id . includes ( ' Products/ ' ) ) ; } ) ;

Usage with TypeScript

TypeScript typings are embedded into the package (see types property in package.json ).

export class Product { constructor { } } import { Product } from " models/product " ; import { DocumentStore , IDocumentStore , IDocumentSession , IDocumentQuery , DocumentConstructor , QueryOperators } from ' ravendb ' ; ; store . conventions . registerEntityType ( Product ) ; ; store . initialize ( ) ; ( ) ( ) ;

Working with secured server

Fill auth options object. Pass contents of the pem/pfx certificate, specify its type and (optionally) a passphrase:

const { DocumentStore , Certificate } = require ( ' ravendb ' ) ; const certificate = ` -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- ` ; let authOptions = { certificate , type : " pem " , password : " my passphrase " } ;

PFX certificates content should be passed as Buffer object:

const { DocumentStore } = require ( ' ravendb ' ) ; const fs = require ( ' fs ' ) ; const certificate = ' ./cert.pfx ' ; let authOptions = { certificate : fs . readFileSync ( certificate ) , type : " pfx " , password : ' my passphrase ' } ;

Pass auth options as third argument to DocumentStore constructor:

let store = new DocumentStore ( ' url ' , ' databaseName ' , authOptions ) ; store . initialize ( ) ;

Building

npm install npm run build

Running tests