npm install --save ravendb- Require
DocumentStoreclass from package
const {DocumentStore} = require('ravendb');or
const DocumentStore = require('ravendb').default;or (using ES6 / Typescript imports)
import DocumentStore from 'ravendb';- Initialize document store (this can be kept as a global object)
const store = DocumentStore.create('database url', 'default database name'); store.initialize();- Open a session
const session = store.openSession();- Call
saveChanges()when you'll finish working with a session
session .load('Users/1-A') .then((user) => { user.password = PBKDF2('new password'); return session.store(user); }) .then(() => session.saveChanges()) .then(() => { // here you can finish request });- You can use callbacks
session .load('Users/1-A', (user) => { user.password = PBKDF2('new password'); session.store(user, null, () => { session.saveChanges(() => { // here session is complete }); }); })- You can use promises as well
session .load('Users/1-A') .then((user) => { user.password = PBKDF2('new password'); return session.store(user); }) .then(() => session.saveChanges()) .then(() => { // here session is complete });- With
colibrary or frameworks using it (such asAdonisJS) you canyieldcalls
const co = require('co'); // ... co(function * () { session = store.openSession(); let user = yield store.load('Users/1-A'); user.password = PBKDF2('new password'); yield session.store(user); yield session.saveChanges(); // here session is complete });- Also client is supporting
async/awaitstuff
async () => { session = store.openSession(); let user = await store.load('Users/1-A'); user.password = PBKDF2('new password'); await session.store(user); await session.saveChanges(); // here session is complete })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') }; product = await session.store(product, 'Products/'); console.log(product.id); // will output Products/<some number>-<some letter (server node tag)> e.g. Products/1-A await session.saveChanges();product = await session.load('Products/1-A'); console.log(product.title); // iPhone X console.log(product.id); // Products/1-Aproduct = await session.load('Products/1-A'); product.in_stock = false; product.last_update = new Date(); await session.store(product); await session.saveChanges(); product = await session.load('Products/1-A'); console.log(product.in_stock); // false console.log(product.last_update); // outputs current dateproduct = await session.load('Products/1-A'); await session.delete(product); // or you can just do // await session.delete('Products/1-A'); await session.saveChanges(); product = await session.load('Products/1-A'); console.log(product); // undefined- Create
DocumentQueryinstance usingquery()method of session:
query = session.query({ collection: 'Products', // specify which collection you'd like to query // optionally you may specify an index name for querying // indexName: 'PopularProductsWithViewsCount' });- Apply conditions, ordering etc. Query supports chaining calls:
const {DocumentStore, QueryOperators} = require('ravendb'); // ... query .waitForNonStaleResults() .usingDefaultOperator(QueryOperators.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:
let documents = await query.all();| Method | RQL / description |
|---|---|
selectFields(fields: string[], projections?: string[]): IDocumentQuery<T>; | SELECT field1 [AS projection1], ... |
distinct(): IDocumentQuery<T>; | SELECT DISTINCT |
whereEquals <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName = <value> |
whereNotEquals <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName != <value> |
whereIn <V extends ConditionValue>( fieldName: string, values: V[], exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName IN (<value1>, <value2>, ...) |
whereStartsWith <V extends ConditionValue>( fieldName: string, value: V) : IDocumentQuery<T>; | WHERE startsWith(fieldName, '<value>') |
whereEndsWith <V extends ConditionValue>( fieldName: string, value: V) : IDocumentQuery<T>; | WHERE endsWith(fieldName, '<value>') |
whereBetween <V extends ConditionValue>( fieldName: string, start: V, end: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName BETWEEN <start> AND <end> |
whereGreaterThan <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName > <value> |
whereGreaterThanOrEqual <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName >= <value> |
whereLessThan <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName < <value> |
whereLessThanOrEqual <V extends ConditionValue>( fieldName: string, value: V, exact?: boolean) : IDocumentQuery<T>; | WHERE fieldName <= <value> |
whereExists(fieldName: string) : IDocumentQuery<T>; | WHERE exists(fieldName) |
containsAny <V extends ConditionValue>( fieldName: string, values: V[]) : IDocumentQuery<T>; | WHERE fieldName IN (<value1>, <value2>, ...) |
containsAll <V extends ConditionValue>( fieldName: string, values: V[]) : IDocumentQuery<T>; | WHERE fieldName ALL IN (<value1>, <value2>, ...) |
search(fieldName: string, searchTerms: string, operator?: SearchOperator) : IDocumentQuery<T>; | Performs full-text search |
openSubclause(): IDocumentQuery<T>; | Opens subclause ( |
closeSubclause(): IDocumentQuery<T>; | Closes subclause ) |
negateNext(): IDocumentQuery<T>; | Adds NOT before next condition |
andAlso(): IDocumentQuery<T>; | Adds AND before next condition |
orElse(): IDocumentQuery<T>; | Adds OR before next condition |
usingDefaultOperator (operator: QueryOperator) : IDocumentQuery<T>; | 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 |
orderBy(field: string, ordering?: OrderingType) : IDocumentQuery<T>; | ORDER BY field [DESC] |
randomOrdering(seed?: string) : IDocumentQuery<T>; | ORDER BY random() |
take(count: number) : IDocumentQuery<T>; | Limits the number of result entries to count |
skip(count: number) : IDocumentQuery<T>; | Skips first count results |
async first(callback? : EntityCallback): Promise; | Returns first document from result set |
async single(callback? : EntityCallback): Promise; | Returns single document matching query criteria. If there are no such document or more then one - throws an InvalidOperationException |
async all(callback? : QueryResultsCallback): Promise; | Returns all documents from result set (considering take() / skip() options) |
async count(callback? : EntitiesCountCallback): Promise; | Returns count of all documents matching query criteria (non-considering take() / skip() options) |
Condition value can be a string, number, boolean, null value or Date object:
type ConditionValue = string | number | boolean | Date | null;- Define your model as class. Attributes should be just public properties:
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() }); } }- For store model just pass it's instance without specifying collection prefix (e.g.
Products/). Collection name will be detected automatically by model's class name
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); // true console.log(product.id.includes('Products/')); // true await session.saveChanges();- When loading document, you need to use
session.load()secondoptionsparam. Pass class constructor asdocumentTypeoption:
let product = await session.load('Products/1-A', {documentType: Product}); console.log(product instanceof Product); // true console.log(product.id); // Products/1-A- When querying documents, pass class constructor to
documentTypeoption ofsession.query({ ... }):
let products = await session.query({ collection: 'Products', documentType: Product }).all(); products.forEach((product) => { console.log(product instanceof Product); // true console.log(product.id.includes('Products/')); // true });Also you can set global models class resolver (something like class autoloader in PHP). It should be a callback function accepting a name of the model class and returning its constructor:
store.conventions.addDocumentInfoResolver({ resolveConstructor: (className) => require(`./relative/path/to/models/${className}`)[className] }); session = store.openSession(); let product = await session.load('Products/1-A'); console.log(product instanceof Product); // true console.log(product.id); // Products/1-A let products = await session.query({ collection: 'Products' }).all(); products.forEach((product) => { console.log(product instanceof Product); // true console.log(product.id.includes('Products/')); // true });All datatype definitions you can find in lib/ravendb-node.d.ts. An example of CRUD operations and querying documents you may find below:
// file models/Product.ts export class Product { constructor( public id: string = null, public title: string = '', public price: number = 0, public currency: string = 'USD', public storage: number = 0, public manufacturer: string = '', public in_stock: boolean = false, public last_update: Date = null ) {} } export default Product; // file app.ts import {DocumentStore, IDocumentStore, IDocumentSession, IDocumentQuery, DocumentConstructor, QueryOperators} from 'ravendb'; const store: IDocumentStore = DocumentStore.create('database url', 'database name'); let session: IDocumentSession; store.initialize(); store.conventions.addDocumentInfoResolver({ resolveConstructor: (typeName: string): DocumentConstructor => <DocumentConstructor>require(`./models/${typeName}`)[typeName] }); (async (): Promise<void> => { let product: Product = new Product( null, 'iPhone X', 999.99, 'USD', 64, 'Apple', true, new Date('2017-10-01T00:00:00') ); product = await session.store<Product>(product); console.log(product instanceof Product); // true console.log(product.id.includes('Products/')); // true await session.saveChanges(); product = await session.load<Product>('Products/1-A'); console.log(product instanceof Product); // true console.log(product.id); // Products/1-A let products: Product[] = await session .query<Product>({ collection: 'Products' }) .waitForNonStaleResults() .usingDefaultOperator(QueryOperators.And) .whereEquals<string>('manufacturer', 'Apple') .whereEquals<boolean>('in_stock', true) .whereBetween<Date>('last_update', new Date('2017-10-01T00:00:00'), new Date()) .whereGreaterThanOrEqual<number>('storage', 64) .all(); products.forEach((product: Product): void => { console.log(product instanceof Product); // true console.log(product.id.includes('Products/')); // true }); })();- Fill auth options object. Pass contents of the pem/pfx certificate, specify its type and (optionally) passphrase:
const {DocumentStore, Certificate} = require('ravendb'); const certificate = ` -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- `; let authOptions = { certificate: certificate, type: Certificate.Pem, password: 'my passphrase' // optional };Pfx certificates content should be passed as Buffer object:
const {DocumentStore, Certificate} = require('ravendb'); const fs = require('fs'); const certificate = './cert.pfx'; let authOptions = { certificate: fs.readFileSync(certificate), type: Certificate.Pfx, password: 'my passphrase' // optional };- Pass auth options as third argument of
DocumentStore.create:
let store = DocumentStore.create( 'database url', 'default database name', authOptions ); store.initialize();- if no auth options was provided and you're trying to work with secured server, an
NotSupportedExceptionwill be thrown during store initialization - if certificate is invalid or doesn't have permissions for specific operations, an
AuthorizationExceptionwill be thrown during working with sessions/querying/sending operations
You can attach binary files to documents.
- For attach a file, use
PutAttachmentOperation. Pass document id, attachment name (it can be just a file name), content type and file contents as anBufferobject:
const {DocumentStore, PutAttachmentOperation} = require('ravendb'); const path = require('path'); const fs = require('fs'); // ... const fileName = './iphone-x.png'; await store.operations.send( new PutAttachmentOperation( 'Products/1-A', path.basename(fileName), fs.readFileSync(fileName), 'image/png' ) ); - For read an attachment, use
GetAttachmentOperation. Pass document id and attachment name. File contents will be stored as anBufferobject insidestreamproperty of response:
const {DocumentStore, PutAttachmentOperation, AttachmentTypes} = require('ravendb'); const fs = require('fs'); // ... const fileName = 'iphone-x.png'; let attachmentResult = await store.operations.send( new GetAttachmentOperation( 'Products/1-A', fileName, AttachmentTypes.Document ) ); fs.writeFileSync(`./${fileName}`, attachmentResult.stream);- For delete an attachment, use
DeleteAttachmentOperation. Pass document id and attachment name.
const {DocumentStore, DeletAttachmentOperation} = require('ravendb'); // ... const fileName = 'iphone-x.png'; await store.operations.send( new DeleteAttachmentOperation( 'Products/1-A', fileName ) ); By default document id is stored onto id property of document. But you can define which name of id property should be for specific document types.
- Define custom document id property name resolver as callback function. It accepts document type and should return id property name:
// models.item.js class Item { constructor(Id = null, Title = "", Options = []) { this.Id = Id; this.Title = Title; this.Options = Options; } } exports.Item = Item; // index.js const {DocumentStore} = require('ravendb'); const {Item} = require('./models/item'); const resolveIdProperty = (typeName) => { switch (typeName) { case Item.name: return 'Id'; // ... } };- Pass this callback to
resolveIdPropertyoption ofconventions.addDocumentInfoResolver:
store.conventions.addDocumentInfoResolver({ resolveIdProperty });- Now client will read/fill
Idproperty with document id while doing CRUD operations:
let session = store.openSession(); await session.store(new Item(null, 'First Item', [1, 2, 3])); await session.saveChanges(); console.log(item.Id); // Items/1-A session = store.openSession(); let item = await session.load('Items/1-A'); console.log(item.Id); // Items/1-A console.log(item.Title); // First Item console.log(item.Options); // [1, 2, 3]You can define custom serializers if you need to implement your own logic for convert attributes names/values for specific document types.
- Define your serializer as object with
onSerialized/onUnserializedmethods:
const serializer = { onSerialized: (serialized) => { }, onUnserialized: (serialized) => { } };Where serialized attribute has the following structure:
interface ISerialized<T extends Object = IRavenObject> { source: object | T; target?: object | T; originalAttribute: string; serializedAttribute: string; originalValue: any; serializedValue: any; attributePath: string; metadata?: object; nestedObjectTypes?: IRavenObject<DocumentConstructor>; }- Store target attribute name/value into the
serializedAttribute/serializedValueproperties ofserializedparameter:
function capitalize(string) { return string.charAt(0).toUpperCase() + string.substring(1); } function uncapitalize(string) { return string.charAt(0).toLowerCase() + string.substring(1); } const serializer = { onSerialized: (serialized) => { switch (serialized.metadata['Raven-Node-Type']) { case Item.name: serialized.serializedAttribute = uncapitalize(serialized.originalAttribute); if ('Items' === serialized.originalAttribute) { serialized.serializedValue = serialized.originalValue.join(","); } break; // ... } }, onUnserialized: (serialized) => { switch (serialized.metadata['Raven-Node-Type']) { case Item.name: serialized.serializedAttribute = capitalize(serialized.originalAttribute); if ('items' === serialized.originalAttribute) { serialized.serializedValue = serialized.originalValue.split(",").map(parseInt); } break; // ... } } };- Pass your serializer object to
conventions.addAttributeSerializer:
const {DocumentStore, GetDocumentCommand} = require('ravendb'); store.conventions.addAttributeSerializer(serializer); let sesssion = store.openSession(); await session.store(new Item(null, 'First Item', [1, 2, 3])); await session.saveChanges(); session = store.openSession(); let item = await session.load('Items/1-A'); console.log(item.Id); // Items/1-A console.log(item.Title); // First Item console.log(item.Options); // [1, 2, 3] let response = await store.getRequestExecutor().execute(new GetDocumentCommand('Items/1-A')); let rawDocument = response.Results[0]; console.log(rawDocument['@metadata']['@id']); // Items/1-A console.log(rawDocument.title); // First Item console.log(rawDocument.options); // "1,2,3"npm run buildnpm test -- -h 192.168.5.44 [-p 8080] [-c path/to/certificate.pem(pfx)] [-t DocumentSerializing [-f]]| Option | Description |
|---|---|
-h or --ravendb-host= | Database host |
-p or --ravendb-port= | Database port. 8080 by default |
-c or --ravendb-certificate= | Path to .pem or .pfx certificate. If specified, test runner will use https protocol |
-t or --test= | Test name. For run multiple test, specify each test in separate --test= option. By default runs all tests |
-f or --no-fixtures | Skip executing database fixtures (create test database, put test indexes etc). Can be usable for tests which doesn't executes raven commands (e.g. DocumentSerializing) |