update readme, persistence tests and typings

Timothée Rebours 3 years ago
parent 16ab521465
commit 5a294be2dd
  1. 44
      README.md
  2. 18
      index.d.ts
  3. 160
      test/persistence.test.js
  4. 4
      test/utils.test.js

@ -44,7 +44,7 @@ version.
Don't hesitate to open an issue if it breaks something in your project. 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 The rest of the readme will only show the Promise-based API, the full
documentation is available in the [`docs`](./docs) directory of the repository. documentation is available in the [`docs`](./API.md) directory of the repository.
### Creating/loading a database ### Creating/loading a database
@ -87,20 +87,38 @@ await db.users.loadDatabaseAsync()
await db.robots.loadDatabaseAsync() await db.robots.loadDatabaseAsync()
``` ```
### 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.
### Persistence ### Persistence
Under the hood, NeDB's [persistence](./docs/Persistence.md) uses an append-only Under the hood, NeDB's [persistence](./API.md#Persistence) uses an append-only
format, meaning that all updates and deletes actually result in lines added at 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 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 compacted (i.e. put back in the one-line-per-document format) every time you
load each database within your application. load each database within your application.
**Breaking change**: since v3.0.0, calling methods of `yourDatabase.persistence`
is deprecated. The same functions exists directly on the `Datastore`.
You can manually call the compaction function You can manually call the compaction function
with [`yourDatabase#persistence#compactDatafileAsync`](./API.md#Persistence+compactDatafileAsync). with [`yourDatabase#compactDatafileAsync`](./API.md#Datastore+compactDatafileAsync).
You can also set automatic compaction at regular intervals You can also set automatic compaction at regular intervals
with [`yourDatabase#persistence#setAutocompactionInterval`](./API.md#Persistence+setAutocompactionInterval), with [`yourDatabase#setAutocompactionInterval`](./API.md#Datastore+setAutocompactionInterval),
and stop automatic compaction with [`yourDatabase#persistence#stopAutocompaction`](./API.md#Persistence+stopAutocompaction). and stop automatic compaction with [`yourDatabase#stopAutocompaction`](./API.md#Datastore+stopAutocompaction).
### Inserting documents ### Inserting documents
@ -385,7 +403,7 @@ const docs = await db.findAsync({
[`Datastore#findAsync`](./API.md#Datastore+findAsync), [`Datastore#findAsync`](./API.md#Datastore+findAsync),
[`Datastore#findOneAsync`](./API.md#Datastore+findOneAsync) and [`Datastore#findOneAsync`](./API.md#Datastore+findOneAsync) and
[`Datastore#countAsync`](./API.md#Datastore+countAsync) don't [`Datastore#countAsync`](./API.md#Datastore+countAsync) don't
actually return a `Promise`, but a [`Cursor`](./docs/Cursor.md) which is a actually return a `Promise`, but a [`Cursor`](./API.md#Cursor) which is a
[`Thenable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#thenable_objects) [`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. which calls [`Cursor#execAsync`](./API.md#Cursor+execAsync) when awaited.
@ -783,16 +801,16 @@ for the `browser` field. And this is [done by default by Metro](https://github.c
for the `react-native` field. for the `react-native` field.
This is done for: This is done for:
- the [storage module](./docs/storage.md) which uses Node.js `fs`. It is - the [storage module](./lib/storage.js) which uses Node.js `fs`. It is
[replaced in the browser](./docs/storageBrowser.md) by one that uses [replaced in the browser](./browser-version/lib/storage.browser.js) by one that uses
[localforage](https://github.com/localForage/localForage), and [localforage](https://github.com/localForage/localForage), and
[in `react-native`](./docs/storageBrowser.md) by one that uses [in `react-native`](./browser-version/lib/storage.react-native.js) by one that uses
[@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage) [@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage)
- the [customUtils module](./docs/customUtilsNode.md) which uses Node.js - the [customUtils module](./browser-version/lib/customUtils.js) which uses Node.js
`crypto` module. It is replaced by a good enough shim to generate ids that uses `Math.random()`. `crypto` module. It is replaced by a good enough shim to generate ids that uses `Math.random()`.
- the [byline module](./docs/byline.md) which uses Node.js `stream` - the [byline module](./browser-version/lib/byline.js) which uses Node.js `stream`
(a fork of [`node-byline`](https://github.com/jahewson/node-byline) included in (a fork of [`node-byline`](https://github.com/jahewson/node-byline) included in
the repo because it is unmaintained). It isn't used int the browser nor the repo because it is unmaintained). It isn't used in the browser nor
react-native versions, therefore it is shimmed with an empty object. react-native versions, therefore it is shimmed with an empty object.
## Performance ## Performance
@ -849,7 +867,7 @@ to make it manageable:
pollute the code. pollute the code.
* Don't forget tests for your new feature. Also don't forget to run the whole * 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. test suite before submitting to make sure you didn't introduce regressions.
* Update the JSDoc and regenerate [the markdown files](./docs). * Update the JSDoc and regenerate [the markdown docs](./API.md).
* Update the readme accordingly. * Update the readme accordingly.
## License ## License

18
index.d.ts vendored

@ -24,6 +24,18 @@ declare class Nedb<G = any> extends EventEmitter {
loadDatabaseAsync(): Promise<void>; loadDatabaseAsync(): Promise<void>;
dropDatabase(callback?: (err: Error |null) => void): void;
dropDatabaseAsync(): Promise<void>;
compactDatafile(callback?: (err: Error |null) => void): void;
compactDatafileAsync(): Promise<void>;
setAutocompactionInterval(interval: number): void;
stopAutocompaction(): void;
getAllData<T extends G>(): T[]; getAllData<T extends G>(): T[];
ensureIndex(options: Nedb.EnsureIndexOptions, callback?: (err: Error | null) => void): void; ensureIndex(options: Nedb.EnsureIndexOptions, callback?: (err: Error | null) => void): void;
@ -101,8 +113,6 @@ declare namespace Nedb {
afterSerialization?(line: string): string; afterSerialization?(line: string): string;
corruptAlertThreshold?: number; corruptAlertThreshold?: number;
compareStrings?(a: string, b: string): number; compareStrings?(a: string, b: string): number;
/** @deprecated */
nodeWebkitAppName?: string;
} }
interface UpdateOptions { interface UpdateOptions {
@ -123,9 +133,13 @@ declare namespace Nedb {
} }
interface Persistence { interface Persistence {
/** @deprecated */
compactDatafile(): void; compactDatafile(): void;
/** @deprecated */
compactDatafileAsync(): Promise<void>; compactDatafileAsync(): Promise<void>;
/** @deprecated */
setAutocompactionInterval(interval: number): void; setAutocompactionInterval(interval: number): void;
/** @deprecated */
stopAutocompaction(): void; stopAutocompaction(): void;
} }
} }

@ -10,6 +10,8 @@ const Persistence = require('../lib/persistence')
const storage = require('../lib/storage') const storage = require('../lib/storage')
const { execFile, fork } = require('child_process') const { execFile, fork } = require('child_process')
const { callbackify } = require('util') const { callbackify } = require('util')
const { existsCallback } = require('./utils.test')
const { ensureFileDoesntExistAsync } = require('../lib/storage')
const Readable = require('stream').Readable const Readable = require('stream').Readable
const { assert } = chai const { assert } = chai
@ -1054,4 +1056,162 @@ describe('Persistence', function () {
}) })
}) })
}) // ==== End of 'Prevent dataloss when persisting data' ==== }) // ==== End of 'Prevent dataloss when persisting data' ====
describe('dropDatabase', function () {
it('deletes data in memory', done => {
const inMemoryDB = new Datastore({ inMemoryOnly: true })
inMemoryDB.insert({ hello: 'world' }, err => {
assert.equal(err, null)
inMemoryDB.dropDatabase(err => {
assert.equal(err, null)
assert.equal(inMemoryDB.getAllData().length, 0)
return done()
})
})
})
it('deletes data in memory & on disk', done => {
d.insert({ hello: 'world' }, err => {
if (err) return done(err)
d.dropDatabase(err => {
if (err) return done(err)
assert.equal(d.getAllData().length, 0)
existsCallback(testDb, bool => {
assert.equal(bool, false)
done()
})
})
})
})
it('check that executor is drained before drop', done => {
for (let i = 0; i < 100; i++) {
d.insert({ hello: 'world' }) // no await
}
d.dropDatabase(err => { // it should await the end of the inserts
if (err) return done(err)
assert.equal(d.getAllData().length, 0)
existsCallback(testDb, bool => {
assert.equal(bool, false)
done()
})
})
})
it('check that autocompaction is stopped', done => {
d.setAutocompactionInterval(5000)
d.insert({ hello: 'world' }, err => {
if (err) return done(err)
d.dropDatabase(err => {
if (err) return done(err)
assert.equal(d.autocompactionIntervalId, null)
assert.equal(d.getAllData().length, 0)
existsCallback(testDb, bool => {
assert.equal(bool, false)
done()
})
})
})
})
it('check that we can reload and insert afterwards', done => {
d.insert({ hello: 'world' }, err => {
if (err) return done(err)
d.dropDatabase(err => {
if (err) return done(err)
assert.equal(d.getAllData().length, 0)
existsCallback(testDb, bool => {
assert.equal(bool, false)
d.loadDatabase(err => {
if (err) return done(err)
d.insert({ hello: 'world' }, err => {
if (err) return done(err)
assert.equal(d.getAllData().length, 1)
d.compactDatafile(err => {
if (err) return done(err)
existsCallback(testDb, bool => {
assert.equal(bool, true)
done()
})
})
})
})
})
})
})
})
it('check that we can dropDatatabase if the file is already deleted', done => {
callbackify(ensureFileDoesntExistAsync)(testDb, err => {
if (err) return done(err)
existsCallback(testDb, bool => {
assert.equal(bool, false)
d.dropDatabase(err => {
if (err) return done(err)
existsCallback(testDb, bool => {
assert.equal(bool, false)
done()
})
})
})
})
})
it('Check that TTL indexes are reset', done => {
d.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 10 })
const date = new Date()
d.insert({ hello: 'world', expire: new Date(date.getTime() - 1000 * 20) }, err => { // expired by 10 seconds
if (err) return done(err)
d.find({}, (err, docs) => {
if (err) return done(err)
assert.equal(docs.length, 0) // the TTL makes it so that the document is not returned
d.dropDatabase(err => {
if (err) return done(err)
assert.equal(d.getAllData().length, 0)
existsCallback(testDb, bool => {
assert.equal(bool, false)
d.loadDatabase(err => {
if (err) return done(err)
d.insert({ hello: 'world', expire: new Date(date.getTime() - 1000 * 20) }, err => {
if (err) return done(err)
d.find({}, (err, docs) => {
if (err) return done(err)
assert.equal(docs.length, 1) // the TTL makes it so that the document is not returned
d.compactDatafile(err => {
if (err) return done(err)
existsCallback(testDb, bool => {
assert.equal(bool, true)
done()
})
})
})
})
})
})
})
})
})
})
it('Check that the buffer is reset', done => {
d.dropDatabase(err => {
if (err) return done(err)
// these 3 will hang until load
d.insert({ hello: 'world' })
d.insert({ hello: 'world' })
d.insert({ hello: 'world' })
assert.equal(d.getAllData().length, 0)
d.dropDatabase(err => {
if (err) return done(err)
d.insert({ hi: 'world' })
d.loadDatabase(err => {
if (err) return done(err)
assert.equal(d.getAllData().length, 1)
assert.equal(d.getAllData()[0].hi, 'world')
done()
})
})
})
})
}) // ==== End of 'dropDatabase' ====
}) })

@ -33,10 +33,14 @@ const wait = delay => new Promise(resolve => {
}) })
const exists = path => fs.access(path, fsConstants.FS_OK).then(() => true, () => false) const exists = path => fs.access(path, fsConstants.FS_OK).then(() => true, () => false)
// eslint-disable-next-line node/no-callback-literal
const existsCallback = (path, callback) => fs.access(path, fsConstants.FS_OK).then(() => callback(true), () => callback(false))
module.exports.whilst = whilst module.exports.whilst = whilst
module.exports.apply = apply module.exports.apply = apply
module.exports.waterfall = waterfall module.exports.waterfall = waterfall
module.exports.each = each module.exports.each = each
module.exports.wait = wait module.exports.wait = wait
module.exports.exists = exists module.exports.exists = exists
module.exports.existsCallback = existsCallback
module.exports.callbackify = callbackify module.exports.callbackify = callbackify

Loading…
Cancel
Save