The JavaScript Database, for Node.js, nw.js, electron and the browser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nedb/README.md

902 lines
35 KiB

9 years ago
<img src="http://i.imgur.com/9O1xHFb.png" style="width: 25%; height: 25%; float: left;">
12 years ago
## The JavaScript Database
9 years ago
This module is a fork of [nedb](https://github.com/louischatriot/nedb)
written by Louis Chatriot.
12 years ago
Since the original maintainer doesn't support this package anymore, we forked it
and maintain it for the needs of [Seald](https://www.seald.io).
10 years ago
3 years ago
**Embedded persistent or in memory database for Node.js, Electron and browsers,
100% JavaScript, no binary dependency**. API is a subset of MongoDB's and it's
[plenty fast](#speed).
9 years ago
3 years ago
## Installation
3 years ago
Module name on npm is [`@seald-io/nedb`](https://www.npmjs.com/package/@seald-io/nedb).
```
4 years ago
npm install @seald-io/nedb
12 years ago
```
12 years ago
3 years ago
Then to import, you just have to:
```js
const Datastore = require('@seald-io/nedb')
```
## Documentation
The API is a subset of MongoDB's API (the most used operations).
Since version [3.0.0](./CHANGELOG.md#300---2022-03-16), NeDB provides a Promise-based equivalent for each function
3 years ago
which is suffixed with `Async`, for example `loadDatabaseAsync`.
The original callback-based interface is still available, fully retro-compatible
(as far as the test suites can tell) and are a shim to this Promise-based
version.
Don't hesitate to open an issue if it breaks something in your project.
The rest of the readme will only show the Promise-based API, the full
documentation is available in the [`API.md`](./API.md) file at the root of the
repository. It is generated by running `npm run generateDocs:markdown`.
* [Creating/loading a database](#creatingloading-a-database)
* [Dropping a database](#dropping-a-database)
* [Persistence](#persistence)
* [Inserting documents](#inserting-documents)
* [Finding documents](#finding-documents)
* [Basic Querying](#basic-querying)
* [Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex)](#operators-lt-lte-gt-gte-in-nin-ne-stat-regex)
* [Array fields](#array-fields)
* [Logical operators $or, $and, $not, $where](#logical-operators-or-and-not-where)
* [Sorting and paginating](#sorting-and-paginating)
* [Projections](#projections)
* [Counting documents](#counting-documents)
* [Updating documents](#updating-documents)
* [Removing documents](#removing-documents)
* [Indexing](#indexing)
* [Other environments](#other-environments)
12 years ago
### Creating/loading a database
12 years ago
You can use NeDB as an in-memory only datastore or as a persistent datastore.
One datastore is the equivalent of a MongoDB collection. The constructor is used
as follows [`new Datastore(options)` where `options` is an object](./API.md#new_Datastore_new).
3 years ago
If the Datastore is persistent (if you give it [`options.filename`](./API.md#Datastore+filename),
you'll need to load the database using [Datastore#loadDatabaseAsync](./API.md#Datastore+loadDatabaseAsync),
or using [`options.autoload`](./API.md#Datastore+autoload).
9 years ago
12 years ago
```javascript
12 years ago
// Type 1: In-memory only datastore (no need to load the database)
const Datastore = require('@seald-io/nedb')
const db = new Datastore()
12 years ago
12 years ago
// Type 2: Persistent datastore with manual loading
const Datastore = require('@seald-io/nedb')
const db = new Datastore({ filename: 'path/to/datafile' })
3 years ago
try {
await db.loadDatabaseAsync()
} catch (error) {
// loading has failed
}
// loading has succeeded
12 years ago
// Type 3: Persistent datastore with automatic loading
const Datastore = require('@seald-io/nedb')
3 years ago
const db = new Datastore({ filename: 'path/to/datafile', autoload: true }) // You can await db.autoloadPromise to catch a potential error when autoloading.
// You can issue commands right away
12 years ago
// Of course you can create multiple datastores if you need several
12 years ago
// collections. In this case it's usually a good idea to use autoload for all collections.
3 years ago
db = {}
db.users = new Datastore('path/to/users.db')
db.robots = new Datastore('path/to/robots.db')
12 years ago
3 years ago
// You need to load each database
await db.users.loadDatabaseAsync()
await db.robots.loadDatabaseAsync()
12 years ago
```
### Dropping a database
Since v3.0.0, you can drop the database by using [`Datastore#dropDatabaseAsync`](./API.md#Datastore+dropDatabaseAsync):
```js
const Datastore = require('@seald-io/nedb')
const db = new Datastore()
await d.insertAsync({ hello: 'world' })
await d.dropDatabaseAsync()
assert.equal(d.getAllData().length, 0)
assert.equal(await exists(testDb), false)
```
It is not recommended to keep using an instance of Datastore when its database
has been dropped as it may have some unintended side effects.
9 years ago
### Persistence
11 years ago
Under the hood, NeDB's [persistence](./API.md#Persistence) uses an append-only
3 years ago
format, meaning that all updates and deletes actually result in lines added at
the end of the datafile, for performance reasons. The database is automatically
compacted (i.e. put back in the one-line-per-document format) every time you
load each database within your application.
**Breaking change**: [since v3.0.0](./CHANGELOG.md#300---unreleased), calling methods of `yourDatabase.persistence`
is deprecated. The same functions exists directly on the `Datastore`.
You can manually call the compaction function
with [`yourDatabase#compactDatafileAsync`](./API.md#Datastore+compactDatafileAsync).
You can also set automatic compaction at regular intervals
with [`yourDatabase#setAutocompactionInterval`](./API.md#Datastore+setAutocompactionInterval),
and stop automatic compaction with [`yourDatabase#stopAutocompaction`](./API.md#Datastore+stopAutocompaction).
11 years ago
12 years ago
### Inserting documents
12 years ago
The native types are `String`, `Number`, `Boolean`, `Date` and `null`. You can
also use arrays and subdocuments (objects). If a field is `undefined`, it will
not be saved (this is different from MongoDB which transforms `undefined`
in `null`, something I find counter-intuitive).
If the document does not contain an `_id` field, NeDB will automatically
3 years ago
generate one for you (a 16-characters alphanumerical string). The `_id` of a
document, once set, cannot be modified.
12 years ago
Field names cannot start with '$' or contain the characters '.' and ','.
12 years ago
```javascript
3 years ago
const doc = {
hello: 'world',
n: 5,
today: new Date(),
nedbIsAwesome: true,
notthere: null,
notToBeSaved: undefined, // Will not be saved
fruits: ['apple', 'orange', 'pear'],
infos: { name: '@seald-io/nedb' }
}
3 years ago
try {
const newDoc = await db.insertAsync(doc)
// newDoc is the newly inserted document, including its _id
12 years ago
// newDoc has no key called notToBeSaved since its value was undefined
3 years ago
} catch (error) {
// if an error happens
}
12 years ago
```
You can also bulk-insert an array of documents. This operation is atomic,
meaning that if one insert fails due to a unique constraint being violated, all
changes are rolled back.
11 years ago
```javascript
3 years ago
const newDocs = await db.insertAsync([{ a: 5 }, { a: 42 }])
// Two documents were inserted in the database
// newDocs is an array with these documents, augmented with their _id
11 years ago
// If there is a unique constraint on field 'a', this will fail
3 years ago
try {
await db.insertAsync([{ a: 5 }, { a: 42 }, { a: 5 }])
} catch (error) {
11 years ago
// err is a 'uniqueViolated' error
// The database was not modified
3 years ago
}
11 years ago
```
12 years ago
### Finding documents
12 years ago
3 years ago
Use `findAsync` to look for multiple documents matching you query, or `findOneAsync` to
look for one specific document. You can select documents based on field equality
or use comparison operators (`$lt`, `$lte`, `$gt`, `$gte`, `$in`, `$nin`, `$ne`)
. You can also use logical operators `$or`, `$and`, `$not` and `$where`. See
below for the syntax.
You can use regular expressions in two ways: in basic querying in place of a
string, or with the `$regex` operator.
12 years ago
11 years ago
You can sort and paginate results using the cursor API (see below).
You can use standard projections to restrict the fields to appear in the
results (see below).
10 years ago
12 years ago
#### Basic querying
Basic querying means are looking for documents whose fields match the ones you
specify. You can use regular expression to match strings. You can use the dot
notation to navigate inside nested documents, arrays, arrays of subdocuments and
to match a specific element of an array.
12 years ago
```javascript
// Let's say our datastore contains the following collection
12 years ago
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }
12 years ago
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }
12 years ago
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
12 years ago
// { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }
11 years ago
// { _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }
12 years ago
// Finding all planets in the solar system
3 years ago
const docs = await db.findAsync({ system: 'solar' })
// docs is an array containing documents Mars, Earth, Jupiter
// If no document is found, docs is equal to []
12 years ago
12 years ago
// Finding all planets whose name contain the substring 'ar' using a regular expression
3 years ago
const docs = await db.findAsync({ planet: /ar/ })
// docs contains Mars and Earth
12 years ago
12 years ago
// Finding all inhabited planets in the solar system
3 years ago
const docs = await db.findAsync({ system: 'solar', inhabited: true })
// docs is an array containing document Earth only
12 years ago
12 years ago
// Use the dot-notation to match fields in subdocuments
3 years ago
const docs = await db.findAsync({ 'humans.genders': 2 })
// docs contains Earth
12 years ago
11 years ago
// Use the dot-notation to navigate arrays of subdocuments
3 years ago
const docs = await db.findAsync({ 'completeData.planets.name': 'Mars' })
// docs contains document 5
11 years ago
3 years ago
const docs = await db.findAsync({ 'completeData.planets.name': 'Jupiter' })
// docs is empty
const docs = await db.findAsync({ 'completeData.planets.0.name': 'Earth' })
// docs contains document 5
// If we had tested against 'Mars' docs would be empty because we are matching against a specific array element
11 years ago
12 years ago
// You can also deep-compare objects. Don't confuse this with dot-notation!
3 years ago
const docs = await db.findAsync({ humans: { genders: 2 } })
// docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true }
12 years ago
12 years ago
// Find all documents in the collection
3 years ago
const docs = await db.findAsync({})
12 years ago
12 years ago
// The same rules apply when you want to only find one document
3 years ago
const doc = await db.findOneAsync({ _id: 'id1' })
// doc is the document Mars
// If no document is found, doc is null
12 years ago
```
#### Operators ($lt, $lte, $gt, $gte, $in, $nin, $ne, $exists, $regex)
The syntax is `{ field: { $op: value } }` where `$op` is any comparison
operator:
12 years ago
* `$lt`, `$lte`: less than, less than or equal
* `$gt`, `$gte`: greater than, greater than or equal
* `$in`: member of. `value` must be an array of values
* `$ne`, `$nin`: not equal, not a member of
* `$exists`: checks whether the document posses the property `field`. `value`
should be true or false
* `$regex`: checks whether a string is matched by the regular expression.
Contrary to MongoDB, the use of `$options` with `$regex` is not supported,
because it doesn't give you more power than regex flags. Basic queries are
more readable so only use the `$regex` operator when you need to use another
operator with it (see example below)
12 years ago
```javascript
12 years ago
// $lt, $lte, $gt and $gte work on numbers and strings
3 years ago
const docs = await db.findAsync({ 'humans.genders': { $gt: 5 } })
// docs contains Omicron Persei 8, whose humans have more than 5 genders (7).
12 years ago
12 years ago
// When used with strings, lexicographical order is used
3 years ago
const docs = await db.findAsync({ planet: { $gt: 'Mercury' } })
// docs contains Omicron Persei 8
12 years ago
// Using $in. $nin is used in the same way
3 years ago
const docs = await db.findAsync({ planet: { $in: ['Earth', 'Jupiter'] } })
// docs contains Earth and Jupiter
12 years ago
// Using $exists
const docs = await db.findAsync({ satellites: { $exists: true } })
3 years ago
// docs contains only Mars
12 years ago
// Using $regex with another operator
3 years ago
const docs = await db.findAsync({
planet: {
$regex: /ar/,
$nin: ['Jupiter', 'Earth']
}
})
3 years ago
// docs only contains Mars because Earth was excluded from the match by $nin
12 years ago
```
#### Array fields
When a field in a document is an array, NeDB first tries to see if the query
value is an array to perform an exact match, then whether there is an
array-specific comparison function (for now there is only `$size`
and `$elemMatch`) being used. If not, the query is treated as a query on every
element and there is a match if at least one element matches.
9 years ago
* `$size`: match on the size of the array
* `$elemMatch`: matches if at least one array element matches the query entirely
12 years ago
```javascript
9 years ago
// Exact match
3 years ago
const docs = await db.findAsync({ satellites: ['Phobos', 'Deimos'] })
// docs contains Mars
const docs = await db.findAsync({ satellites: ['Deimos', 'Phobos'] })
// docs is empty
9 years ago
11 years ago
// Using an array-specific comparison function
// $elemMatch operator will provide match for a document, if an element from the array field satisfies all the conditions specified with the `$elemMatch` operator
3 years ago
const docs = await db.findAsync({
completeData: {
planets: {
$elemMatch: {
name: 'Earth',
number: 3
}
}
}
})
3 years ago
// docs contains documents with id 5 (completeData)
3 years ago
const docs = await db.findAsync({
completeData: {
planets: {
$elemMatch: {
name: 'Earth',
number: 5
}
}
}
})
3 years ago
// docs is empty
// You can use inside #elemMatch query any known document query operator
3 years ago
const docs = await db.findAsync({
completeData: {
planets: {
$elemMatch: {
name: 'Earth',
number: { $gt: 2 }
}
}
}
})
3 years ago
// docs contains documents with id 5 (completeData)
11 years ago
// Note: you can't use nested comparison functions, e.g. { $size: { $lt: 5 } } will throw an error
3 years ago
const docs = await db.findAsync({ satellites: { $size: 2 } })
// docs contains Mars
11 years ago
3 years ago
const docs = await db.findAsync({ satellites: { $size: 1 } })
// docs is empty
11 years ago
12 years ago
// If a document's field is an array, matching it means matching any element of the array
3 years ago
const docs = await db.findAsync({ satellites: 'Phobos' })
// docs contains Mars. Result would have been the same if query had been { satellites: 'Deimos' }
12 years ago
12 years ago
// This also works for queries that use comparison operators
3 years ago
const docs = await db.findAsync({ satellites: { $lt: 'Amos' } })
// docs is empty since Phobos and Deimos are after Amos in lexicographical order
12 years ago
// This also works with the $in and $nin operator
3 years ago
const docs = await db.findAsync({ satellites: { $in: ['Moon', 'Deimos'] } })
// docs contains Mars (the Earth document is not complete!)
12 years ago
```
#### Logical operators $or, $and, $not, $where
You can combine queries using logical operators:
12 years ago
12 years ago
* For `$or` and `$and`, the syntax is `{ $op: [query1, query2, ...] }`.
* For `$not`, the syntax is `{ $not: query }`
* For `$where`, the syntax
is `{ $where: function () { /* object is 'this', return a boolean */ } }`
12 years ago
```javascript
3 years ago
const docs = await db.findAsync({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] })
// docs contains Earth and Mars
12 years ago
3 years ago
const docs = await db.findAsync({ $not: { planet: 'Earth' } })
// docs contains Mars, Jupiter, Omicron Persei 8
12 years ago
3 years ago
const docs = await db.findAsync({ $where: function () { return Object.keys(this) > 6 } })
// docs with more than 6 properties
12 years ago
// You can mix normal queries, comparison queries and logical operators
3 years ago
const docs = await db.findAsync({
$or: [{ planet: 'Earth' }, { planet: 'Mars' }],
inhabited: true
})
3 years ago
// docs contains Earth
12 years ago
```
11 years ago
#### Sorting and paginating
[`Datastore#findAsync`](./API.md#Datastore+findAsync),
[`Datastore#findOneAsync`](./API.md#Datastore+findOneAsync) and
[`Datastore#countAsync`](./API.md#Datastore+countAsync) don't
actually return a `Promise`, but a [`Cursor`](./API.md#Cursor) which is a
3 years ago
[`Thenable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#thenable_objects)
which calls [`Cursor#execAsync`](./API.md#Cursor+execAsync) when awaited.
3 years ago
This pattern allows to chain [`Cursor#sort`](./API.md#Cursor+sort),
[`Cursor#skip`](./API.md#Cursor+skip),
[`Cursor#limit`](./API.md#Cursor+limit) and
[`Cursor#projection`](./API.md#Cursor+projection) and await the result.
11 years ago
```javascript
// Let's say the database contains these 4 documents
// doc1 = { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }
// doc2 = { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }
// doc3 = { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// doc4 = { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }
// No query used means all results are returned (before the Cursor modifiers)
3 years ago
const docs = await db.findAsync({}).sort({ planet: 1 }).skip(1).limit(2)
// docs is [doc3, doc1]
11 years ago
// You can sort in reverse order like this
3 years ago
const docs = await db.findAsync({ system: 'solar' }).sort({ planet: -1 })
// docs is [doc1, doc3, doc2]
11 years ago
// You can sort on one field, then another, and so on like this:
3 years ago
const docs = await db.findAsync({}).sort({ firstField: 1, secondField: -1 })
// ... You understand how this works!
11 years ago
```
10 years ago
#### Projections
3 years ago
You can give `findAsync` and `findOneAsync` an optional second argument, `projections`.
The syntax is the same as MongoDB: `{ a: 1, b: 1 }` to return only the `a`
and `b` fields, `{ a: 0, b: 0 }` to omit these two fields. You cannot use both
modes at the time, except for `_id` which is by default always returned and
which you can choose to omit. You can project on nested documents.
10 years ago
```javascript
// Same database as above
// Keeping only the given fields
3 years ago
const docs = await db.findAsync({ planet: 'Mars' }, { planet: 1, system: 1 })
// docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }]
10 years ago
// Keeping only the given fields but removing _id
3 years ago
const docs = await db.findAsync({ planet: 'Mars' }, {
planet: 1,
system: 1,
_id: 0
})
3 years ago
// docs is [{ planet: 'Mars', system: 'solar' }]
10 years ago
// Omitting only the given fields and removing _id
3 years ago
const docs = await db.findAsync({ planet: 'Mars' }, {
planet: 0,
system: 0,
_id: 0
})
3 years ago
// docs is [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }]
10 years ago
// Failure: using both modes at the same time
3 years ago
const docs = await db.findAsync({ planet: 'Mars' }, { planet: 0, system: 1 })
// err is the error message, docs is undefined
10 years ago
// You can also use it in a Cursor way but this syntax is not compatible with MongoDB
3 years ago
const docs = await db.findAsync({ planet: 'Mars' }).projection({
planet: 1,
system: 1
})
3 years ago
// docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }]
9 years ago
// Project on a nested document
3 years ago
const doc = await db.findOneAsync({ planet: 'Earth' }).projection({
planet: 1,
'humans.genders': 1
})
3 years ago
// doc is { planet: 'Earth', _id: 'id2', humans: { genders: 2 } }
10 years ago
```
11 years ago
### Counting documents
3 years ago
You can use `countAsync` to count documents. It has the same syntax as `findAsync`.
For example:
11 years ago
```javascript
// Count all planets in the solar system
3 years ago
const count = await db.countAsync({ system: 'solar' })
// count equals to 3
11 years ago
// Count all documents in the datastore
3 years ago
const count = await db.countAsync({})
// count equals to 4
11 years ago
```
12 years ago
### Updating documents
[`db.updateAsync(query, update, options)`](./API.md#Datastore+updateAsync)
3 years ago
will update all documents matching `query` according to the `update` rules.
`update` specifies how the documents should be modified. It is either a new
document or a set of modifiers (you cannot use both together):
* A new document will replace the matched docs;
* Modifiers create the fields they need to modify if they don't exist,
and you can apply them to subdocs (see [the API reference]((./API.md#Datastore+updateAsync)))
3 years ago
`options` is an object with three possible parameters:
* `multi` which allows the modification of several documents if set to true.
* `upsert` will insert a new document corresponding if it doesn't exist (either
the `update` is a simple object with no modifiers, or the `query` modified by
the modifiers in the `update`) if set to `true`.
* `returnUpdatedDocs` will return the array of documents matched by the find
query and updated (updated documents will be returned even if the update did not
actually modify them) if set to `true`.
It resolves into an Object with the following properties:
- `numAffected`: how many documents were affected by the update;
- `upsert`: if a document was actually upserted (not always the same as `options.upsert`;
- `affectedDocuments`:
- if `upsert` is `true` the document upserted;
- if `options.returnUpdatedDocs` is `true` either the affected document or, if `options.multi` is `true` an Array of the affected documents, else `null`;
12 years ago
12 years ago
**Note**: you can't change a document's _id.
12 years ago
```javascript
// Let's use the same example collection as in the 'finding document' part
12 years ago
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }
12 years ago
12 years ago
// Replace a document by another
const { numAffected } = await db.updateAsync({ planet: 'Jupiter' }, { planet: 'Pluton' }, {})
// numAffected = 1
3 years ago
// The doc #3 has been replaced by { _id: 'id3', planet: 'Pluton' }
// Note that the _id is kept unchanged, and the document has been replaced
// (the 'system' and inhabited fields are not here anymore)
12 years ago
12 years ago
// Set an existing field's value
const { numAffected } = await db.updateAsync({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true })
// numAffected = 3
3 years ago
// Field 'system' on Mars, Earth, Jupiter now has value 'solar system'
12 years ago
12 years ago
// Setting the value of a non-existing field in a subdocument by using the dot-notation
3 years ago
await db.updateAsync({ planet: 'Mars' }, {
$set: {
'data.satellites': 2,
'data.red': true
}
3 years ago
}, {})
// Mars document now is { _id: 'id1', system: 'solar', inhabited: false
// , data: { satellites: 2, red: true }
// }
// Not that to set fields in subdocuments, you HAVE to use dot-notation
// Using object-notation will just replace the top-level field
await db.updateAsync({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {})
// Mars document now is { _id: 'id1', system: 'solar', inhabited: false
// , data: { satellites: 3 }
// }
// You lost the 'data.red' field which is probably not the intended behavior
12 years ago
11 years ago
// Deleting a field
3 years ago
await db.updateAsync({ planet: 'Mars' }, { $unset: { planet: true } }, {})
// Now the document for Mars doesn't contain the planet field
// You can unset nested fields with the dot notation of course
11 years ago
12 years ago
// Upserting a document
const { numAffected, affectedDocuments, upsert } = await db.updateAsync({ planet: 'Pluton' }, {
planet: 'Pluton',
inhabited: false
3 years ago
}, { upsert: true })
// numAffected = 1, affectedDocuments = { _id: 'id5', planet: 'Pluton', inhabited: false }, upsert = true
3 years ago
// A new document { _id: 'id5', planet: 'Pluton', inhabited: false } has been added to the collection
12 years ago
// If you upsert with a modifier, the upserted doc is the query modified by the modifier
12 years ago
// This is simpler than it sounds :)
3 years ago
await db.updateAsync({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true })
// A new document { _id: 'id5', planet: 'Pluton', distance: 38 } has been added to the collection
12 years ago
// If we insert a new document { _id: 'id6', fruits: ['apple', 'orange', 'pear'] } in the collection,
// let's see how we can modify the array field atomically
// $push inserts new elements at the end of the array
3 years ago
await db.updateAsync({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {})
// Now the fruits array is ['apple', 'orange', 'pear', 'banana']
12 years ago
// $pop removes an element from the end (if used with 1) or the front (if used with -1) of the array
3 years ago
await db.updateAsync({ _id: 'id6' }, { $pop: { fruits: 1 } }, {})
// Now the fruits array is ['apple', 'orange']
// With { $pop: { fruits: -1 } }, it would have been ['orange', 'pear']
12 years ago
// $addToSet adds an element to an array only if it isn't already in it
12 years ago
// Equality is deep-checked (i.e. $addToSet will not insert an object in an array already containing the same object)
12 years ago
// Note that it doesn't check whether the array contained duplicates before or not
3 years ago
await db.updateAsync({ _id: 'id6' }, { $addToSet: { fruits: 'apple' } }, {})
// The fruits array didn't change
// If we had used a fruit not in the array, e.g. 'banana', it would have been added to the array
12 years ago
// $pull removes all values matching a value or even any NeDB query from the array
3 years ago
await db.updateAsync({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {})
// Now the fruits array is ['orange', 'pear']
await db.updateAsync({ _id: 'id6' }, { $pull: { fruits: { $in: ['apple', 'pear'] } } }, {})
// Now the fruits array is ['orange']
12 years ago
// $each can be used to $push or $addToSet multiple values at once
// This example works the same way with $addToSet
3 years ago
await db.updateAsync({ _id: 'id6' }, { $push: { fruits: { $each: ['banana', 'orange'] } } }, {})
// Now the fruits array is ['apple', 'orange', 'pear', 'banana', 'orange']
9 years ago
// $slice can be used in cunjunction with $push and $each to limit the size of the resulting array.
// A value of 0 will update the array to an empty array. A positive value n will keep only the n first elements
// A negative value -n will keep only the last n elements.
// If $slice is specified but not $each, $each is set to []
3 years ago
await db.updateAsync({ _id: 'id6' }, {
$push: {
fruits: {
$each: ['banana'],
$slice: 2
}
}
3 years ago
})
// Now the fruits array is ['apple', 'orange']
// $min/$max to update only if provided value is less/greater than current value
// Let's say the database contains this document
// doc = { _id: 'id', name: 'Name', value: 5 }
3 years ago
await db.updateAsync({ _id: 'id1' }, { $min: { value: 2 } }, {})
// The document will be updated to { _id: 'id', name: 'Name', value: 2 }
3 years ago
await db.updateAsync({ _id: 'id1' }, { $min: { value: 8 } }, {})
// The document will not be modified
12 years ago
```
12 years ago
### Removing documents
[`db.removeAsync(query, options)`](./API.md#Datastore#removeAsync)
3 years ago
will remove documents matching `query`. Can remove multiple documents if
`options.multi` is set. Returns the `Promise<numRemoved>`.
12 years ago
```javascript
// Let's use the same example collection as in the "finding document" part
12 years ago
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }
12 years ago
// Remove one document from the collection
// options set to {} since the default for multi is false
3 years ago
const { numRemoved } = await db.removeAsync({ _id: 'id2' }, {})
// numRemoved = 1
12 years ago
// Remove multiple documents
3 years ago
const { numRemoved } = await db.removeAsync({ system: 'solar' }, { multi: true })
// numRemoved = 3
// All planets from the solar system were removed
9 years ago
// Removing all documents with the 'match-all' query
3 years ago
const { numRemoved } = await db.removeAsync({}, { multi: true })
12 years ago
```
12 years ago
### Indexing
12 years ago
NeDB supports indexing. It gives a very nice speed boost and can be used to
enforce a unique constraint on a field. You can index any field, including
fields in nested documents using the dot notation. For now, indexes are only
used to speed up basic queries and queries using `$in`, `$lt`, `$lte`, `$gt`
and `$gte`. The indexed values cannot be of type array of object.
2 years ago
**Breaking change**: [since v4.0.0](./CHANGELOG.md#400---2023-01-20), commas (`,`) can no longer be used in indexed field names.
The following is illegal:
```javascript
db.ensureIndexAsync({ fieldName: 'some,field' })
db.ensureIndexAsync({ fieldName: ['some,field', 'other,field'] })
```
This is a side effect of the compound index implementation.
To create an index, use [`datastore#ensureIndexAsync(options)`](./API.md#Datastore+ensureIndexAsync).
3 years ago
It resolves when the index is persisted on disk (if the database is persistent)
and may throw an Error (usually a unique constraint that was violated). It can
be called when you want, even after some data was inserted, though it's best to
call it at application startup. The options are:
* **fieldName** (required): name of the field to index. Use the dot notation to
index a field in a nested document. For a compound index, use an array of field names.
3 years ago
* **unique** (optional, defaults to `false`): enforce field uniqueness.
* **sparse** (optional, defaults to `false`): don't index documents for which
3 years ago
the field is not defined.
* **expireAfterSeconds** (number of seconds, optional): if set, the created
index is a TTL (time to live) index, that will automatically remove documents
3 years ago
when the indexed field value is older than `expireAfterSeconds`.
3 years ago
Note: the `_id` is automatically indexed with a unique constraint.
3 years ago
You can remove a previously created index with
[`datastore#removeIndexAsync(fieldName)`](./API.md#Datastore+removeIndexAsync).
12 years ago
```javascript
3 years ago
try {
await db.ensureIndexAsync({ fieldName: 'somefield' })
} catch (error) {
// If there was an error, error is not null
}
12 years ago
// Using a unique constraint with the index
3 years ago
await db.ensureIndexAsync({ fieldName: 'somefield', unique: true })
12 years ago
12 years ago
// Using a sparse unique index
3 years ago
await db.ensureIndexAsync({
fieldName: 'somefield',
unique: true,
sparse: true
3 years ago
})
// Using a compound index
await db.ensureIndexAsync({ fieldName: ["field1", "field2"] });
3 years ago
try {
// Format of the error message when the unique constraint is not met
await db.insertAsync({ somefield: '@seald-io/nedb' })
// works
await db.insertAsync({ somefield: '@seald-io/nedb' })
//rejects
} catch (error) {
// error is { errorType: 'uniqueViolated',
// key: 'name',
// message: 'Unique constraint violated for key name' }
}
11 years ago
// Remove index on field somefield
3 years ago
await db.removeIndexAsync('somefield')
9 years ago
9 years ago
// Example of using expireAfterSeconds to remove documents 1 hour
9 years ago
// after their creation (db's timestampData option is true here)
3 years ago
await db.ensureIndex({
fieldName: 'createdAt',
expireAfterSeconds: 3600
3 years ago
})
9 years ago
// You can also use the option to set an expiration date like so
3 years ago
await db.ensureIndex({
fieldName: 'expirationDate',
expireAfterSeconds: 0
3 years ago
})
// Now all documents will expire when system time reaches the date in their
// expirationDate field
12 years ago
```
12 years ago
3 years ago
## Other environments
NeDB runs on Node.js (it is tested on Node 12, 14 and 16), the browser (it is
tested on the latest version of Chrome) and React-Native using
[@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage).
3 years ago
### Browser bundle
The npm package contains a bundle and its minified counterpart for the browser.
They are located in the `browser-version/out` directory. You only need to require `nedb.js`
or `nedb.min.js` in your HTML file and the global object `Nedb` can be used
right away, with the same API as the server version:
12 years ago
```
<script src="nedb.min.js"></script>
<script>
var db = new Nedb(); // Create an in-memory only datastore
9 years ago
db.insert({ planet: 'Earth' }, function (err) {
db.find({}, function (err, docs) {
// docs contains the two planets Earth and Mars
});
12 years ago
});
</script>
```
If you specify a `filename`, the database will be persistent, and automatically
3 years ago
select the best storage method available using [localforage](https://github.com/localForage/localForage)
(IndexedDB, WebSQL or localStorage) depending on the browser. In most cases that
means a lot of data can be stored, typically in hundreds of MB.
**WARNING**: the storage system changed between
v1.3 and v1.4 and is NOT back-compatible! Your application needs to resync
client-side when you upgrade NeDB.
10 years ago
3 years ago
NeDB uses modern Javascript features such as `async`, `Promise`, `class`, `const`
, `let`, `Set`, `Map`, ... The bundle does not polyfill these features. If you
need to target another environment, you will need to make your own bundle.
### Using the `browser` and `react-native` fields
NeDB uses the `browser` and `react-native` fields to replace some modules by an
environment specific shim.
The way this works is by counting on the bundler that will package NeDB to use
this fields. This is [done by default by Webpack](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
for the `browser` field. And this is [done by default by Metro](https://github.com/facebook/metro/blob/c21daba415ea26511e157f794689caab9abe8236/packages/metro/src/ModuleGraph/node-haste/Package.js#L108)
for the `react-native` field.
This is done for:
- the [storage module](./lib/storage.js) which uses Node.js `fs`. It is
[replaced in the browser](./browser-version/lib/storage.browser.js) by one that uses
3 years ago
[localforage](https://github.com/localForage/localForage), and
[in `react-native`](./browser-version/lib/storage.react-native.js) by one that uses
3 years ago
[@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage)
- the [customUtils module](./browser-version/lib/customUtils.js) which uses Node.js
3 years ago
`crypto` module. It is replaced by a good enough shim to generate ids that uses `Math.random()`.
- the [byline module](./browser-version/lib/byline.js) which uses Node.js `stream`
3 years ago
(a fork of [`node-byline`](https://github.com/jahewson/node-byline) included in
the repo because it is unmaintained). It isn't used in the browser nor
3 years ago
react-native versions, therefore it is shimmed with an empty object.
12 years ago
12 years ago
## Performance
12 years ago
### Speed
NeDB is not intended to be a replacement of large-scale databases such as
MongoDB, and as such was not designed for speed. That said, it is still pretty
fast on the expected datasets, especially if you use indexing. On a typical,
not-so-fast dev machine, for a collection containing 10,000 documents, with
indexing:
9 years ago
* Insert: **10,680 ops/s**
* Find: **43,290 ops/s**
* Update: **8,000 ops/s**
* Remove: **11,750 ops/s**
12 years ago
You can run these simple benchmarks by executing the scripts in the `benchmarks`
folder. Run them with the `--help` flag to see how they work.
12 years ago
12 years ago
### Memory footprint
A copy of the whole database is kept in memory. This is not much on the expected
kind of datasets (20MB for 10,000 2KB documents).
12 years ago
## Use in other services
* An ODM for NeDB: [follicle](https://github.com/seald/follicle)
* A layer to add a promise-only interface: [nedb-promises](https://www.npmjs.com/package/nedb-promises)
## Modernization
This fork of NeDB will be incrementally updated to:
3 years ago
* [ ] cleanup the benchmark and update the performance statistics;
* [ ] remove deprecated features;
* [ ] add a way to change the `Storage` module by dependency injection, which will
pave the way to cleaner browser react-native versions (cf https://github.com/seald/nedb/pull/19).
* [x] use `async` functions and `Promises` instead of callbacks with `async@0.2.6`;
* [x] expose a `Promise`-based interface;
* [x] remove the `underscore` dependency;
## Pull requests guidelines
If you submit a pull request, thanks! There are a couple rules to follow though
to make it manageable:
* The pull request should be atomic, i.e. contain only one feature. If it
contains more, please submit multiple pull requests. Reviewing massive, 1000
loc+ pull requests is extremely hard.
* Likewise, if for one unique feature the pull request grows too large (more
than 200 loc tests not included), please get in touch first.
* Please stick to the current coding style. It's important that the code uses a
coherent style for readability (this package uses [`standard`](https://standardjs.com/)).
* Do not include stylistic improvements ('housekeeping'). If you think one part
deserves lots of housekeeping, use a separate pull request so as not to
pollute the code.
* Don't forget tests for your new feature. Also don't forget to run the whole
test suite before submitting to make sure you didn't introduce regressions.
* Update the JSDoc and regenerate [the markdown docs](./API.md).
9 years ago
* Update the readme accordingly.
11 years ago
## License
12 years ago
See [License](LICENSE.md)