npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ravendb

v7.2.2

Published

RavenDB client for Node.js

Readme

Node.js client for RavenDB NoSQL Database

NPM

build status Known Vulnerabilities

Installation

npm install --save ravendb

Releases

Documentation

Getting started

  1. Require the DocumentStore class from the ravendb package
const { DocumentStore } = require('ravendb');

or (using ES6 / Typescript imports)

import { DocumentStore } from 'ravendb';
  1. Initialize the document store (you should have a single DocumentStore instance per application)
const store = new DocumentStore('http://live-test.ravendb.net', 'databaseName');
store.initialize();
  1. Open a session
const session = store.openSession();
  1. Call saveChanges() when you're done
session
 .load('users/1-A') // Load document
 .then((user) => {
   user.password = PBKDF2('new password'); // Update data 
 })
 .then(() => session.saveChanges()) // Save changes
 .then(() => {
     // Data is now persisted
     // You can proceed e.g. finish web request
  });
  1. When you have finished using the session and the document store objects,
    make sure to dispose of them properly to free up resources:
session.dispose();
store.dispose();

Supported asynchronous call types

Most methods on the session object are asynchronous and return a Promise.
Either use async & await or .then() with callback functions.

  1. async / await
const session = store.openSession();
let user = await session.load('users/1-A');
user.password = PBKDF2('new password');
await session.saveChanges();
  1. .then & callback functions
session.load('Users/1-A')
    .then((user) => {
        user.password = PBKDF2('new password');
    })
    .then(() => session.saveChanges())
    .then(() => {
        // here session is complete
    });
Related tests:

async and await
then and callbacks

CRUD example

Store documents

let product = {
    id: null,
    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/1-A');
console.log(product.id); // products/1-A
await session.saveChanges();
Related tests:

store()
ID generation - session.store()
store document with @metadata
storing docs with same ID in same session should throw

Load documents

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

load()

Load documents with include

// users/1
// {
//      "name": "John",
//      "kids": ["users/2", "users/3"]
// }

const session = store.openSession();
const user1 = await session
    .include("kids")
    .load("users/1");
    // Document users/1 and all docs referenced in "kids"
    // will be fetched from the server in a single request.

const user2 = await session.load("users/2"); // this won't call server again

assert.ok(user1);
assert.ok(user2);
assert.equal(session.advanced.numberOfRequests, 1);
Related tests:

can load with includes
loading data with include
loading data with passing includes

Update 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);    // false
console.log(product.last_update); // the current date
Related tests:

update document
update document metadata

Delete documents

  1. 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); // null
  1. Using document ID
await session.delete('products/1-A');
Related tests:

delete doc by entity
delete doc by ID
onBeforeDelete is called before delete by ID
cannot delete untracked entity
loading deleted doc returns null

Query documents

  1. Use query() session method:

Query by collection:

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

Query by index name:

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

Query by index:

const query = session.query(Product, Product_ByName);

Query by entity type:

import { User } from "./models";
const query = session.query(User);
  1. Build up the query - apply search conditions, set ordering, etc.
    Query supports chaining calls:
query
    .waitForNonStaleResults()
    .usingDefaultOperator('AND')
    .whereEquals('manufacturer', 'Apple')
    .whereEquals('in_stock', true)
    .whereBetween('last_update', new Date('2022-11-01T00:00:00'), new Date())
    .orderBy('price');
  1. Execute the query to get results:
const results = await query.all(); // get all results
// ...
const firstResult = await query.first(); // gets first result
// ...
const single = await query.single();  // gets single result 

Query methods overview

selectFields() - projections using a single field

// RQL
// from users select name

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

// Sample results
// John, Stefanie, Thomas
Related tests:

projections single field
query single property
retrieve camel case with projection
can_project_id_field

selectFields() - projections using multiple fields

// RQL
// from users select name, age

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

// Sample results
// [ { name: 'John', age: 30 },
//   { name: 'Stefanie', age: 25 },
//   { name: 'Thomas', age: 25 } ]
Related tests:

projections multiple fields
query with projection
retrieve camel case with projection
can_project_id_field

distinct()

// RQL
// from users select distinct age

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

// Sample results
// [ 30, 25 ]
Related tests:

distinct
query distinct

whereEquals() / whereNotEquals()

// RQL
// from users where age = 30 

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

// Saple results
// [ User {
//    name: 'John',
//    age: 30,
//    kids: [...],
//    registeredAt: 2017-11-10T23:00:00.000Z } ]
Related tests:

where equals
where not equals

whereIn()

// RQL
// from users where name in ("John", "Thomas")

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

// Sample results
// [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [...],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
Related tests:

where in
query with where in

whereStartsWith() / whereEndsWith()

// RQL
// from users where startsWith(name, 'J')

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

// Sample results
// [ User {
//    name: 'John',
//    age: 30,
//    kids: [...],
//    registeredAt: 2017-11-10T23:00:00.000Z } ]
Related tests:

query with where clause

whereBetween()

// RQL
// from users where registeredAt between '2016-01-01' and '2017-01-01'

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

// Sample results
// [ User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
Related tests:

where between
query with where between

whereGreaterThan() / whereGreaterThanOrEqual() / whereLessThan() / whereLessThanOrEqual()

// RQL
// from users where age > 29

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

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: [...],
//   id: 'users/1-A' } ]
Related tests:

where greater than
query with where less than
query with where less than or equal
query with where greater than
query with where greater than or equal

whereExists()

Checks if the field exists.

// RQL
// from users where exists("age")

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

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: [...],
//   id: 'users/1-A' } ]
Related tests:

where exists
query where exists

containsAny() / containsAll()

// RQL
// from users where kids in ('Mara')

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

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
Related tests:

where contains any
queries with contains

search()

Perform full-text search.

// RQL
// from users where search(kids, 'Mara')

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

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
Related tests:

search()
query search with or
query_CreateClausesForQueryDynamicallyWithOnBeforeQueryEvent

openSubclause() / closeSubclause()

// RQL
// from users where exists(kids) or (age = 25 and name != Thomas)

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

// Sample results
// [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: ["Dmitri", "Mara"]
//     id: 'users/1-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]
Related tests:

subclause
working with subclause

not()

// RQL
// from users where age != 25

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

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
Related tests:

not()
query where not

orElse() / andAlso()

// RQL
// from users where exists(kids) or age < 30

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

// Sample results
//  [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]
Related tests:

orElse
working with subclause

usingDefaultOperator()

If neither andAlso() nor orElse() is called then the default operator between the query filtering conditions will be AND .
You can override that with usingDefaultOperator which must be called before any other where conditions.

// RQL
// from users where exists(kids) or age < 29

// Query
await session.query({ collection: "users" })
    .usingDefaultOperator("OR") // override the default 'AND' operator
    .whereExists("kids")
    .whereLessThan("age", 29)
    .all();

// Sample results
//  [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]
Related tests:

set default operator
AND is used when default operator is not set
set default operator to OR

orderBy() / orderByDesc() / orderByScore() / randomOrdering()

// RQL
// from users order by age

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

// Sample results
// [ User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' } ]
Related tests:

orderBy()
orderByDesc()
query random order
order by AlphaNumeric
query with boost - order by score

take()

Limit the number of query results.

// RQL
// from users order by age

// Query
await session.query({ collection: "users" })
    .orderBy("age") 
    .take(2) // only the first 2 entries will be returned
    .all();

// Sample results
// [ User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
Related tests:

take()
query skip take
canUseOffsetWithCollectionQuery

skip()

Skip a specified number of results from the start.

// RQL
// from users order by age

// Query
await session.query({ collection: "users" })
    .orderBy("age") 
    .take(1) // return only 1 result
    .skip(1) // skip the first result, return the second result
    .all();

// Sample results
// [ User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
Related tests:

skip()
query skip take
canUseOffsetWithCollectionQuery

Getting query statistics

Use the statistics() method to obtain query statistics.

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

// Sample results
// QueryStatistics {
//   isStale: false,
//   durationInMs: 744,
//   totalResults: 1,
//   skippedResults: 0,
//   timestamp: 2018-09-24T05:34:15.260Z,
//   indexName: 'Auto/users/Byage',
//   indexTimestamp: 2018-09-24T05:34:15.260Z,
//   lastQueryTime: 2018-09-24T05:34:15.260Z,
//   resultEtag: 8426908718162809000 }
Related tests:

can get stats

all() / first() / single() / count()

all() - returns all results

first() - first result only

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

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

Related tests:

query first and single
query count

Ai agents

Create an AI agent

const agentConfiguration = {
    name: "ravendb-ai-agent",
    connectionStringName: "<Your connection string name>",
    systemPrompt: `
        You work for a human experience manager.
        The manager uses your services to find which employee earned the largest profit
        for the company and suggest a reward for this employee.
        The manager provides you with the name of a country, or with the word "everything"
        to indicate all countries.

        Steps:
        1. Use a query tool to load all orders sent to the selected country (or all countries).
        2. Calculate which employee made the largest profit.
        3. Use a query tool to learn in what region the employee lives.
        4. Find suitable vacation sites or other rewards based on the employee's region.
        5. Use an action tool to store the employee's ID, profit, and reward suggestions in the database.

        When you're done, return these details in your answer to the user as well.
    `,
    sampleObject: JSON.stringify({
        employeeID: "the ID of the employee that made the largest profit",
        profit: "the profit the employee made",
        suggestedReward: "your suggestions for a reward"
    }),
    parameters: [
        {
            name: "country",
            description: "A specific country that orders were shipped to, or 'everywhere' to look at all countries"
        }
    ],
    maxModelIterationsPerCall: 3,
    chatTrimming: {
        tokens: {
            maxTokensBeforeSummarization: 32768,
            maxTokensAfterSummarization: 1024
        }
    },
    // Queries the agent can use
    queries: [
        {
            name: "retrieve-orders-sent-to-a-specific-country",
            description: "Retrieve all orders sent to a specific country",
            query: "from Orders as O where O.ShipTo.Country == $country select O.Employee, O.Lines.Quantity",
            parametersSampleObject: "{}"
        },
        {
            name: "retrieve-performer-living-region",
            description: "Retrieve an employee's country, city, and region by employee ID",
            query: "from Employees as E where id() == $employeeId select E.Address.Country, E.Address.City, E.Address.Region",
            parametersSampleObject: "{ \"employeeId\": \"embed the employee's ID here\" }"
        }
    ],
    // Actions the agent can perform
    actions: [
        {
            name: "store-performer-details",
            description: "Store the employee ID, profit, and suggested reward in the database.",
            parametersSampleObject: "{ \"employeeID\": \"embed the employee's ID here\", \"profit\": \"embed the employee's profit here\", \"suggestedReward\": \"embed your suggestions for a reward here\" }"
        }
    ]
};

const agent = await store.ai.createAgent(agentConfiguration);
Related tests:

create an agent
update an agent

Run a Conversation with Tools

const chat = store.ai.conversation(agent.identifier, "Performers/", {
    parameters: {
        country: "France"
    }
});

// Register handler for action tool: "store-performer-details"
chat.handle("store-performer-details", async (req, performer) => {
    const session = store.openSession();
    const rewarded = new Performer(performer.employeeID, performer.profit, performer.suggestedReward);

    await session.store(rewarded);
    await session.saveChanges();
    session.dispose();

    return "done"; // return indication that the action succeeded
});

// Ask the agent to suggest a reward
chat.setUserPrompt("send a few suggestions to reward the employee that made the largest profit");

// Run the conversation
const llmResponse = await chat.run();

console.log("Agent response:", llmResponse);

if (llmResponse.status === "Done") console.log("Conversation finished.")

Stream a Conversation Response

Stream the agent's response in real-time to provide immediate feedback to users.

const chat = store.ai.conversation(agent.identifier, "Performers/", {
    parameters: {country: "France"}
});

// Register action handler
chat.handle("store-performer-details", async (req, performer) => {
    const session = store.openSession();
    await session.store(performer);
    await session.saveChanges();
    session.dispose();
    return {success: true};
});

chat.setUserPrompt("Find the employee with largest profit and suggest rewards");

// Stream the "suggestedReward" property
let chunkedText = "";
const answer = await chat.stream("suggestedReward", async (chunk) => {
    // Called for each streamed chunk
    chunkedText += chunk;
});

console.log("chunkedText", chunkedText);

console.log("Final answer:", answer);

Attachments

Store attachments

const doc = new User({ name: "John" });

// Store a dcoument, the entity will be tracked.
await session.store(doc);

// Get read stream or buffer to store
const fileStream = fs.createReadStream("../photo.png");

// Store attachment using entity
session.advanced.attachments.store(doc, "photo.png", fileStream, "image/png");

// OR store attachment using document ID
session.advanced.attachments.store(doc.id, "photo.png", fileStream, "image/png");

// Persist all changes
await session.saveChanges();
Related tests:

store attachment
can put attachments
checkIfHasChangesIsTrueAfterAddingAttachment
store many attachments and docs with bulk insert

Get attachments

// Get an attachment
const attachment = await session.advanced.attachments.get(documentId, "photo.png")

// Attachment.details contains information about the attachment:
//     { 
//       name: 'photo.png',
//       documentId: 'users/1-A',
//       contentType: 'image/png',
//       hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=',
//       changeVector: '"A:3-K5TR36dafUC98AItzIa6ow"',
//       size: 4579 
//     }

// Attachment.data is a Readable. See https://nodejs.org/api/stream.html#class-streamreadable
attachment.data
    .pipe(fs.createWriteStream("photo.png"))
    .on("finish", () => next());
Related tests:

get attachment
can get & delete attachments

Check if attachment exists

await session.advanced.attachments.exists(doc.id, "photo.png");
// true

await session.advanced.attachments.exists(doc.id, "not_there.avi");
// false
Related tests:

attachment exists
attachment exists 2

Get attachment names

// Use a loaded entity to determine attachments' names
await session.advanced.attachments.getNames(doc);

// Sample results:
// [ { name: 'photo.png',
//     hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=',
//     contentType: 'image/png',
//     size: 4579 } ]
Related tests:

get attachment names
get attachment names 2

Remote Attachments

Store attachments in external cloud storage (S3, Azure) while keeping document metadata in RavenDB.

Configure remote attachments with S3

// Create remote attachments configuration for S3
const configuration = new RemoteAttachmentsConfiguration();
const destination = new RemoteAttachmentsDestinationConfiguration();
destination.disabled = false;

const s3Settings = new RemoteAttachmentsS3Settings();
s3Settings.bucketName = "my-bucket";
s3Settings.awsAccessKey = "your-access-key";
s3Settings.awsSecretKey = "your-secret-key";
s3Settings.awsRegionName = "us-east-1";
s3Settings.remoteFolderName = "production/attachments/2024";  // Optional
destination.s3Settings = s3Settings;

configuration.destinations["S3-Backup"] = destination;

await store.maintenance.send(new ConfigureRemoteAttachmentsOperation(configuration));

Configure remote attachments with Azure

// Create remote attachments configuration for Azure Blob Storage
const configuration = new RemoteAttachmentsConfiguration();
const destination = new RemoteAttachmentsDestinationConfiguration();
destination.disabled = false;

const azureSettings = new RemoteAttachmentsAzureSettings();
azureSettings.storageContainer = "attachments-container";
azureSettings.accountName = "mystorageaccount";
azureSettings.accountKey = "your-account-key";
destination.azureSettings = azureSettings;

configuration.destinations["Azure-Archive"] = destination;

await store.maintenance.send(new ConfigureRemoteAttachmentsOperation(configuration));

Configure multiple destinations

// Configure both S3 and Azure destinations
const configuration = new RemoteAttachmentsConfiguration();

// S3 destination
const s3Destination = new RemoteAttachmentsDestinationConfiguration();
s3Destination.disabled = false;
const s3Settings = new RemoteAttachmentsS3Settings();
s3Settings.bucketName = "s3-bucket";
s3Settings.awsAccessKey = "test-key";
s3Settings.awsSecretKey = "test-secret";
s3Destination.s3Settings = s3Settings;

// Azure destination
const azureDestination = new RemoteAttachmentsDestinationConfiguration();
azureDestination.disabled = false;
const azureSettings = new RemoteAttachmentsAzureSettings();
azureSettings.storageContainer = "azure-container";
azureSettings.accountName = "azureaccount";
azureSettings.accountKey = "azure-key";
azureDestination.azureSettings = azureSettings;

configuration.destinations["S3-Backup"] = s3Destination;
configuration.destinations["Azure-Archive"] = azureDestination;

// Optional: configure processing settings
configuration.checkFrequencyInSec = 300;
configuration.maxItemsToProcess = 1000;
configuration.concurrentUploads = 5;

await store.maintenance.send(new ConfigureRemoteAttachmentsOperation(configuration));

Bulk insert with remote attachments

// Bulk insert documents with remote attachments
const bulkInsert = store.bulkInsert();

for (let i = 0; i < 5; i++) {
    const order = {
        company: `Company ${i}`,
        orderedAt: new Date(2024, 0, i + 1)
    };
    await bulkInsert.store(order, `orders/${i + 1}`);
}

// Upload attachments to remote storage
const remoteAt = new Date(Date.now() + 60000);
for (let i = 0; i < 5; i++) {
    const orderId = `orders/${i + 1}`;
    const attachmentData = Buffer.from(`Attachment data for order ${i + 1}`);
    const remoteParams = new RemoteAttachmentParameters(
        "S3-Backup",  // destination identifier
        remoteAt      // upload time
    );

    await bulkInsert.attachmentsFor(orderId)
        .store(`invoice-${i + 1}.pdf`, attachmentData, "application/pdf", remoteParams);
}

await bulkInsert.finish();

Mixed local and remote attachments

// Bulk insert with both local and remote attachments
const bulkInsert = store.bulkInsert();
const remoteAt = new Date(Date.now() + 120000);

for (let i = 0; i < 4; i++) {
    const order = { company: `Company ${i}`, orderedAt: new Date() };
    await bulkInsert.store(order, `orders/${i}`);

    const attachmentData = Buffer.from(`Attachment ${i}`);

    if (i % 2 === 0) {
        // Store in remote storage
        const remoteParams = new RemoteAttachmentParameters("S3-Backup", remoteAt);
        await bulkInsert.attachmentsFor(`orders/${i}`)
            .store(`remote-${i}.dat`, attachmentData, "application/octet-stream", remoteParams);
    } else {
        // Store locally in RavenDB
        await bulkInsert.attachmentsFor(`orders/${i}`)
            .store(`local-${i}.dat`, attachmentData, "application/octet-stream");
    }
}

await bulkInsert.finish();
Related tests:

configure S3 remote attachments
configure Azure remote attachments
configure multiple destinations
configure with frequency and upload settings
bulk insert with remote parameters
mixed local and remote attachments

TimeSeries

Store time series

const session = store.openSession();

// Create a document with time series
await session.store({ name: "John" }, "users/1");
const tsf = session.timeSeriesFor("users/1", "heartbeat");

// Append a new time series entry
tsf.append(new Date(), 120);

await session.saveChanges();
Related tests:

can use time series
canCreateSimpleTimeSeries
usingDifferentTags
canStoreAndReadMultipleTimestamps
canStoreLargeNumberOfValues
shouldDeleteTimeSeriesUponDocumentDeletion

Get time series for document

const session = store.openSession();

// Get time series for document by time series name
const tsf = session.timeSeriesFor("users/1", "heartbeat");

// Get all time series entries
const heartbeats = await tsf.get();
Related tests:

canCreateSimpleTimeSeries
canStoreLargeNumberOfValues
canRequestNonExistingTimeSeriesRange
canGetTimeSeriesNames2
canSkipAndTakeTimeSeries

Bulk Insert

// Create a bulk insert instance from the DocumentStore
const bulkInsert = store.bulkInsert();

// Store multiple documents
for (const name of ["Anna", "Maria", "Miguel", "Emanuel", "Dayanara", "Aleida"]) {
    const user = new User({ name });
    await bulkInsert.store(user);
    // The data stored in bulkInsert will be streamed to the server in batches 
}

// Sample documents stored:
// User { name: 'Anna', id: 'users/1-A' }
// User { name: 'Maria', id: 'users/2-A' }
// User { name: 'Miguel', id: 'users/3-A' }
// User { name: 'Emanuel', id: 'users/4-A' }
// User { name: 'Dayanara', id: 'users/5-A' }
// User { name: 'Aleida', id: 'users/6-A' }

// Call finish to send all remaining data to the server
await bulkInsert.finish();
Related tests:

bulk insert example
simple bulk insert should work
bulk insert can be aborted
can modify metadata with bulk insert

Changes API

Listen for database changes e.g. document changes.

// Subscribe to change notifications
const changes = store.changes();

// Subscribe for all documents, or for specific collection (or other database items)
const docsChanges = changes.forAllDocuments();

// Handle changes events 
docsChanges.on("data", change => {
    // A sample change data recieved:
    // { type: 'Put',
    //   id: 'users/1-A',
    //   collectionName: 'Users',
    //   changeVector: 'A:2-QCawZTDbuEa4HUBORhsWYA' }
});

docsChanges.on("error", err => {
    // handle errors
})

{
    const session = store.openSession();
    await session.store(new User({ name: "Starlord" }));
    await session.saveChanges();
}

// ...
// Dispose the changes instance when you're done
changes.dispose();
Related tests:

listen to changes
can obtain single document changes
can obtain all documents changes
can obtain notification about documents starting with
can obtain notification about documents in collection

Streaming

Stream documents by ID prefix

// Filter streamed results by passing an ID prefix
// The stream() method returns a Node.js ReadableStream
const userStream = await session.advanced.stream("users/");

// Handle stream events with callback functions
userStream.on("data", user => {
    // Get only documents with ID that starts with 'users/' 
    // i.e.: User { name: 'John', id: 'users/1-A' }
});

userStream.on("error", err => {
    // handle errors
})
Related tests:

can stream users by prefix
can stream documents starting with

Stream documents by query

// Define a query
const query = session.query({ collection: "users" }).whereGreaterThan("age", 29);

let streamQueryStats;
// Call stream() to execute the query, it returns a Node.js ReadableStream.
// Can get query stats by passing a stats callback to stream() method
const queryStream = await session.advanced.stream(query, _ => streamQueryStats = _);

// Handle stream events with callback functions
queryStream.on("data", user => {
    // Only documents matching the query are received
    // These entities are Not tracked by the session
});

// Can get query stats by using an event listener
queryStream.once("stats", queryStats => {
    // Sample stats:
    // { resultEtag: 7464021133404493000,
    //   isStale: false,
    //   indexName: 'Auto/users/Byage',
    //   totalResults: 1,
    //   indexTimestamp: 2018-10-01T09:04:07.145Z }
});

// Stream emits an 'end' event when there is no more data to read
queryStream.on("end", () => {
   // Get info from 'streamQueryStats', the stats object
   const totalResults = streamQueryStats.totalResults;
   const indexUsed = streamQueryStats.indexName;
});

queryStream.on("error", err => {
    // handle errors
});
Related tests:

can stream query and get stats
can stream query results
can stream query results with query statistics
can stream raw query results

Revisions

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

const session = store.openSession();
const user = {
    name: "Marcin",
    age: 30,
    pet: "Cat"
};

// Store a document
await session.store(user, "users/1");
await session.saveChanges();

// Modify the document to create a new revision
user.name = "Roman";
user.age = 40;
await session.saveChanges();

// Get revisions
const revisions = await session.advanced.revisions.getFor("users/1");

// Sample results:
// [ { name: 'Roman',
//     age: 40,
//     pet: 'Cat',
//     '@metadata': [Object],
//     id: 'users/1' },
//   { name: 'Marcin',
//     age: 30,
//     pet: 'Cat',
//     '@metadata': [Object],
//     id: 'users/1' } ]
Related tests:

can get revisions
canGetRevisionsByDate
can handle revisions
canGetRevisionsByChangeVectors

Suggestions

Suggest options for similar/misspelled terms

// Some documents in users collection with misspelled name term
// [ User {
//     name: 'Johne',
//     age: 30,
//     ...
//     id: 'users/1-A' },
//   User {
//     name: 'Johm',
//     age: 31,
//     ...
//     id: 'users/2-A' },
//   User {
//     name: 'Jon',
//     age: 32,
//     ...
//     id: 'users/3-A' },
// ]

// Static index definition
class UsersIndex extends AbstractJavaScriptIndexCreationTask {
    constructor() {
        super();
        this.map(User, doc => {
            return {
                name: doc.name
            }
        });
        
        // Enable the suggestion feature on index-field 'name'
        this.suggestion("name"); 
    }
}

// ...
const session = store.openSession();

// Query for similar terms to 'John'
// Note: the term 'John' itself will Not be part of the results

const suggestedNameTerms = await session.query(User, UsersIndex)
    .suggestUsing(x => x.byField("name", "John")) 
    .execute();

// Sample results:
// { name: { name: 'name', suggestions: [ 'johne', 'johm', 'jon' ] } }
Related tests:

can suggest
canChainSuggestions
canUseAliasInSuggestions
canUseSuggestionsWithAutoIndex
can suggest using linq
can suggest using multiple words
can get suggestions with options

Vector Search

Perform similarity searches using vector embeddings to find documents based on semantic similarity.

Basic vector search

// Query using numeric embedding values
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withField("VectorField"),
        factory => factory.byEmbedding([0.3, 0.4, 0.5])
    )
    .all();

Vector search with similarity and number of candidates

// Search with similarity threshold and candidate limits
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withField("VectorField"),
        factory => factory.byEmbedding([0.3, 0.4, 0.5]),
        {
            similarity: 0.75,
            numberOfCandidates: 50
        }
    )
    .all();

Vector search with text embeddings

// Search using text that will be converted to embeddings
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withText("TextualValue"),
        factory => factory.byText("search text")
    )
    .all();

Vector search with AI task

// Use AI task for text embedding conversion
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withText("TextualValue").usingTask("openai-task"),
        factory => factory.byText("query text")
    )
    .all();

Vector search with quantization

// Search with Int8 quantization for reduced memory footprint
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withEmbedding("EmbeddingSBytes", "Int8"),
        factory => factory.byEmbedding([1, 2, 3]),
        { similarity: 0.75 }
    )
    .all();

Vector search using document reference

// Search for similar documents using an existing document's embeddings
await session.query({ collection: "Dtos" })
    .vectorSearch(
        field => field.withField("VectorField"),
        factory => factory.forDocument("dtos/1")
    )
    .all();

Create index with vector search

// Define index with vector search configuration
class Dtos_ByEmbeddingSingles extends AbstractJavaScriptIndexCreationTask {
    constructor() {
        super();

        this.map("Dtos", p => {
            return {
                "EmbeddingSingles": p.EmbeddingSingles,
            };
        });

        this.vectorField("vectorField", {
            numberOfEdges: 33,
            numberOfCandidatesForIndexing: 43,
            sourceEmbeddingType: "Text",
            destinationEmbeddingType: "Single"
        });
    }
}

// Execute the index
const dtoIndex = new Dtos_ByEmbeddingSingles();
await dtoIndex.execute(store);
Related tests:

basic vector search with numeric embedding
vector search with similarity and candidates
vector search with text field
vector search with AI task
vector search with Int8 quantization
vector search using forDocument
create index with vector search configuration

Embeddings Generation

Automatically generate and maintain vector embeddings for your documents using AI models.

Configure connection string

// Create AI connection string for embeddings
const aiConnectionString = new AiConnectionString();
aiConnectionString.name = "openai-embeddings";
aiConnectionString.modelType = "TextEmbeddings";
aiConnectionString.embeddedSettings = new EmbeddedSettings();

await store.maintenance.send(new PutConnectionStringOperation(aiConnectionString));

Path-based embeddings configuration

// Generate embeddings from specific document fields
const config = new EmbeddingsGenerationConfiguration();
config.name = "Products Embeddings";
config.collection = "Products";
config.connectionStringName = "openai-embeddings";
config.identifier = config.generateIdentifier();

// Define which paths to generate embeddings for
config.embeddingsPathConfigurations = [
    {
        path: "Description",
        chunkingOptions: {
            chunkingMethod: "PlainTextSplitParagraphs",
            maxTokensPerChunk: 256,
            overlapTokens: 32
        }
    },
    {
        path: "Details",
        chunkingOptions: {
            chunkingMethod: "MarkDownSplitParagraphs",
            maxTokensPerChunk: 512,
            overlapTokens: 64
        }
    }
];

config.chunkingOptionsForQuerying = {
    chunkingMethod: "PlainTextSplit",
    maxTokensPerChunk: 256,
    overlapTokens: 0
};

config.quantization = "Int8";

// Add the embeddings generation task
const result = await store.maintenance.send(new AddEmbeddingsGenerationOperation(config));

Script-based embeddings configuration

// Use custom script to combine multiple fields
const config = new EmbeddingsGenerationConfiguration();
config.name = "Articles Embeddings";
config.collection = "Articles";
config.connectionStringName = "openai-embeddings";
config.identifier = config.generateIdentifier();

config.embeddingsTransformation = {
    script: `
        var title = this.Title || "";
        var body = this.Body || "";
        var combined = title + "\\n\\n" + body;
        
        embeddings.generate({
            text: combined,
            field: "ContentEmbedding"
        });
    `,
    chunkingOptions: {
        chunkingMethod: "MarkDownSplitParagraphs",
        maxTokensPerChunk: 512,
        overlapTokens: 64
    }
};

config.chunkingOptionsForQuerying = {
    chunkingMethod: "PlainTextSplit",
    maxTokensPerChunk: 256,
    overlapTokens: 0
};

config.quantization = "Single";

const result = await store.maintenance.send(new AddEmbeddingsGenerationOperation(config));

Update embeddings configuration

// Update existing embeddings generation task
const config = new EmbeddingsGenerationConfiguration();
// ... configure settings ...

config.quantization = "Single";
config.embeddingsCacheExpiration = 30 * 24 * 60 * 60 * 1000; // 30 days

const updateOperation = new UpdateEmbeddingsGenerationOperation(taskId, config);
const updateResult = await store.maintenance.send(updateOperation);

Supported quantization types

// Quantization reduces memory usage
config.quantization = "Int8";    // 8-bit integers
config.quantization = "Single";  // 32-bit floats (default)
config.quantization = "Binary";  // 1-bit binary
Related tests:

path-based embeddings configuration
script-based embeddings configuration
update embeddings generation task
validate quantization types
validate chunking methods
[get ongoing task info](https://github.com/ravendb/ravendb-nodejs-client/blob/v7.0/tes