cleaner test for modes, avoid testing permissions on Windows, document that it will not work on Windows, add typings tests

pull/27/head
Timothée Rebours 3 years ago
parent fc52b75316
commit d3e201cac4
  1. 5
      API.md
  2. 2
      CHANGELOG.md
  3. 1
      index.d.ts
  4. 9
      lib/datastore.js
  5. 24
      lib/persistence.js
  6. 20
      lib/storage.js
  7. 17
      test/persistence.async.test.js
  8. 2
      typings-tests.ts

@ -309,8 +309,7 @@ automatically considered in-memory only. It cannot end with a <code>~</code> whi
perform crash-safe writes. Not used if <code>options.inMemoryOnly</code> is <code>true</code>.</p> perform crash-safe writes. Not used if <code>options.inMemoryOnly</code> is <code>true</code>.</p>
- [.inMemoryOnly] <code>boolean</code> <code> = false</code> - <p>If set to true, no data will be written in storage. This option has - [.inMemoryOnly] <code>boolean</code> <code> = false</code> - <p>If set to true, no data will be written in storage. This option has
priority over <code>options.filename</code>.</p> priority over <code>options.filename</code>.</p>
- [.mode] <code>object</code> - <p>Permissions to use for FS. Only used for - [.modes] <code>object</code> - <p>Permissions to use for FS. Only used for Node.js storage module. Will not work on Windows.</p>
Node.js storage module.</p>
- [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Permissions to use for database files</p> - [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Permissions to use for database files</p>
- [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Permissions to use for database directories</p> - [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Permissions to use for database directories</p>
- [.timestampData] <code>boolean</code> <code> = false</code> - <p>If set to true, createdAt and updatedAt will be created and - [.timestampData] <code>boolean</code> <code> = false</code> - <p>If set to true, createdAt and updatedAt will be created and
@ -838,7 +837,7 @@ with <code>appendfsync</code> option set to <code>no</code>.</p>
- [.corruptAlertThreshold] <code>Number</code> - <p>Optional, threshold after which an alert is thrown if too much data is corrupt</p> - [.corruptAlertThreshold] <code>Number</code> - <p>Optional, threshold after which an alert is thrown if too much data is corrupt</p>
- [.beforeDeserialization] [<code>serializationHook</code>](#serializationHook) - <p>Hook you can use to transform data after it was serialized and before it is written to disk.</p> - [.beforeDeserialization] [<code>serializationHook</code>](#serializationHook) - <p>Hook you can use to transform data after it was serialized and before it is written to disk.</p>
- [.afterSerialization] [<code>serializationHook</code>](#serializationHook) - <p>Inverse of <code>afterSerialization</code>.</p> - [.afterSerialization] [<code>serializationHook</code>](#serializationHook) - <p>Inverse of <code>afterSerialization</code>.</p>
- [.mode] <code>object</code> - <p>Modes to use for FS permissions.</p> - [.modes] <code>object</code> - <p>Modes to use for FS permissions. Will not work on Windows.</p>
- [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Mode to use for files.</p> - [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Mode to use for files.</p>
- [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Mode to use for directories.</p> - [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Mode to use for directories.</p>

@ -13,7 +13,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- An auto-generated JSDoc file is generated: [API.md](./API.md). - An auto-generated JSDoc file is generated: [API.md](./API.md).
- Added `Datastore#dropDatabaseAsync` and its callback equivalent. - Added `Datastore#dropDatabaseAsync` and its callback equivalent.
- The Error given when the `Datastore#corruptAlertThreshold` is reached now has three properties: `dataLength` which is the amount of lines in the database file (excluding empty lines), `corruptItems` which is the amount of corrupted lines, `corruptionRate` which the rate of corruption between 0 and 1. - The Error given when the `Datastore#corruptAlertThreshold` is reached now has three properties: `dataLength` which is the amount of lines in the database file (excluding empty lines), `corruptItems` which is the amount of corrupted lines, `corruptionRate` which the rate of corruption between 0 and 1.
- Added a `mode` option which allows to set the file and / or directory modes, by default, it uses `0o644` for files and `0o755` for directories, which may be breaking. - Added a `modes: { fileMode, dirMode }` option to the Datastore which allows to set the file and / or directory modes, by default, it uses `0o644` for files and `0o755` for directories, which may be breaking.
### Changed ### Changed
- The `corruptionAlertThreshold` now doesn't take into account empty lines, and the error message is slightly changed. - The `corruptionAlertThreshold` now doesn't take into account empty lines, and the error message is slightly changed.

1
index.d.ts vendored

@ -113,6 +113,7 @@ 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;
modes?: {fileMode: number, dirMode: number};
} }
interface UpdateOptions { interface UpdateOptions {

@ -165,10 +165,9 @@ class Datastore extends EventEmitter {
* perform crash-safe writes. Not used if `options.inMemoryOnly` is `true`. * perform crash-safe writes. Not used if `options.inMemoryOnly` is `true`.
* @param {boolean} [options.inMemoryOnly = false] If set to true, no data will be written in storage. This option has * @param {boolean} [options.inMemoryOnly = false] If set to true, no data will be written in storage. This option has
* priority over `options.filename`. * priority over `options.filename`.
* @param {object} [options.mode] Permissions to use for FS. Only used for * @param {object} [options.modes] Permissions to use for FS. Only used for Node.js storage module. Will not work on Windows.
* Node.js storage module. * @param {number} [options.modes.fileMode = 0o644] Permissions to use for database files
* @param {number} [options.mode.fileMode = 0o644] Permissions to use for database files * @param {number} [options.modes.dirMode = 0o755] Permissions to use for database directories
* @param {number} [options.mode.dirMode = 0o755] Permissions to use for database directories
* @param {boolean} [options.timestampData = false] If set to true, createdAt and updatedAt will be created and * @param {boolean} [options.timestampData = false] If set to true, createdAt and updatedAt will be created and
* populated automatically (if not specified by user) * populated automatically (if not specified by user)
* @param {boolean} [options.autoload = false] If used, the database will automatically be loaded from the datafile * @param {boolean} [options.autoload = false] If used, the database will automatically be loaded from the datafile
@ -263,7 +262,7 @@ class Datastore extends EventEmitter {
afterSerialization: options.afterSerialization, afterSerialization: options.afterSerialization,
beforeDeserialization: options.beforeDeserialization, beforeDeserialization: options.beforeDeserialization,
corruptAlertThreshold: options.corruptAlertThreshold, corruptAlertThreshold: options.corruptAlertThreshold,
mode: options.mode modes: options.modes
}) })
// This new executor is ready if we don't use persistence // This new executor is ready if we don't use persistence

@ -46,18 +46,18 @@ class Persistence {
* @param {Number} [options.corruptAlertThreshold] Optional, threshold after which an alert is thrown if too much data is corrupt * @param {Number} [options.corruptAlertThreshold] Optional, threshold after which an alert is thrown if too much data is corrupt
* @param {serializationHook} [options.beforeDeserialization] Hook you can use to transform data after it was serialized and before it is written to disk. * @param {serializationHook} [options.beforeDeserialization] Hook you can use to transform data after it was serialized and before it is written to disk.
* @param {serializationHook} [options.afterSerialization] Inverse of `afterSerialization`. * @param {serializationHook} [options.afterSerialization] Inverse of `afterSerialization`.
* @param {object} [options.mode] Modes to use for FS permissions. * @param {object} [options.modes] Modes to use for FS permissions. Will not work on Windows.
* @param {number} [options.mode.fileMode=0o644] Mode to use for files. * @param {number} [options.modes.fileMode=0o644] Mode to use for files.
* @param {number} [options.mode.dirMode=0o755] Mode to use for directories. * @param {number} [options.modes.dirMode=0o755] Mode to use for directories.
*/ */
constructor (options) { constructor (options) {
this.db = options.db this.db = options.db
this.inMemoryOnly = this.db.inMemoryOnly this.inMemoryOnly = this.db.inMemoryOnly
this.filename = this.db.filename this.filename = this.db.filename
this.corruptAlertThreshold = options.corruptAlertThreshold !== undefined ? options.corruptAlertThreshold : 0.1 this.corruptAlertThreshold = options.corruptAlertThreshold !== undefined ? options.corruptAlertThreshold : 0.1
this.mode = options.mode !== undefined ? options.mode : { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE } this.modes = options.modes !== undefined ? options.modes : { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE }
if (this.mode.fileMode === undefined) this.mode.fileMode = DEFAULT_FILE_MODE if (this.modes.fileMode === undefined) this.modes.fileMode = DEFAULT_FILE_MODE
if (this.mode.dirMode === undefined) this.mode.dirMode = DEFAULT_DIR_MODE if (this.modes.dirMode === undefined) this.modes.dirMode = DEFAULT_DIR_MODE
if ( if (
!this.inMemoryOnly && !this.inMemoryOnly &&
this.filename && this.filename &&
@ -112,7 +112,7 @@ class Persistence {
} }
}) })
await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.mode) await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.modes)
this.db.emit('compaction.done') this.db.emit('compaction.done')
} }
@ -163,7 +163,7 @@ class Persistence {
if (toPersist.length === 0) return if (toPersist.length === 0) return
await storage.appendFileAsync(this.filename, toPersist, { encoding: 'utf8', mode: this.mode.fileMode }) await storage.appendFileAsync(this.filename, toPersist, { encoding: 'utf8', mode: this.modes.fileMode })
} }
/** /**
@ -305,17 +305,17 @@ class Persistence {
// In-memory only datastore // In-memory only datastore
if (this.inMemoryOnly) return if (this.inMemoryOnly) return
await Persistence.ensureDirectoryExistsAsync(path.dirname(this.filename), this.mode.dirMode) await Persistence.ensureDirectoryExistsAsync(path.dirname(this.filename), this.modes.dirMode)
await storage.ensureDatafileIntegrityAsync(this.filename, this.mode.fileMode) await storage.ensureDatafileIntegrityAsync(this.filename, this.modes.fileMode)
let treatedData let treatedData
if (storage.readFileStream) { if (storage.readFileStream) {
// Server side // Server side
const fileStream = storage.readFileStream(this.filename, { encoding: 'utf8', mode: this.mode.fileMode }) const fileStream = storage.readFileStream(this.filename, { encoding: 'utf8', mode: this.modes.fileMode })
treatedData = await this.treatRawStreamAsync(fileStream) treatedData = await this.treatRawStreamAsync(fileStream)
} else { } else {
// Browser // Browser
const rawData = await storage.readFileAsync(this.filename, { encoding: 'utf8', mode: this.mode.fileMode }) const rawData = await storage.readFileAsync(this.filename, { encoding: 'utf8', mode: this.modes.fileMode })
treatedData = this.treatRawData(rawData) treatedData = this.treatRawData(rawData)
} }
// Recreate all indexes in the datafile // Recreate all indexes in the datafile

@ -145,7 +145,7 @@ const flushToStorageAsync = async (options) => {
} else { } else {
filename = options.filename filename = options.filename
flags = options.isDir ? 'r' : 'r+' flags = options.isDir ? 'r' : 'r+'
mode = options.mode || DEFAULT_FILE_MODE mode = options.mode !== undefined ? options.mode : DEFAULT_FILE_MODE
} }
/** /**
* Some OSes and/or storage backends (augmented node fs) do not support fsync (FlushFileBuffers) directories, * Some OSes and/or storage backends (augmented node fs) do not support fsync (FlushFileBuffers) directories,
@ -227,27 +227,27 @@ const writeFileLinesAsync = (filename, lines, mode = DEFAULT_FILE_MODE) => new P
* Fully write or rewrite the datafile, immune to crashes during the write operation (data will not be lost). * Fully write or rewrite the datafile, immune to crashes during the write operation (data will not be lost).
* @param {string} filename * @param {string} filename
* @param {string[]} lines * @param {string[]} lines
* @param {object} [mode={ fileMode: 0o644, dirMode: 0o755 }] * @param {object} [modes={ fileMode: 0o644, dirMode: 0o755 }]
* @param {number} mode.dirMode * @param {number} modes.dirMode
* @param {number} mode.fileMode * @param {number} modes.fileMode
* @return {Promise<void>} * @return {Promise<void>}
* @alias module:storage.crashSafeWriteFileLinesAsync * @alias module:storage.crashSafeWriteFileLinesAsync
*/ */
const crashSafeWriteFileLinesAsync = async (filename, lines, mode = { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE }) => { const crashSafeWriteFileLinesAsync = async (filename, lines, modes = { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE }) => {
const tempFilename = filename + '~' const tempFilename = filename + '~'
await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: mode.dirMode }) await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: modes.dirMode })
const exists = await existsAsync(filename) const exists = await existsAsync(filename)
if (exists) await flushToStorageAsync({ filename, mode: mode.fileMode }) if (exists) await flushToStorageAsync({ filename, mode: modes.fileMode })
await writeFileLinesAsync(tempFilename, lines, mode.fileMode) await writeFileLinesAsync(tempFilename, lines, modes.fileMode)
await flushToStorageAsync({ filename: tempFilename, mode: mode.fileMode }) await flushToStorageAsync({ filename: tempFilename, mode: modes.fileMode })
await renameAsync(tempFilename, filename) await renameAsync(tempFilename, filename)
await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: mode.dirMode }) await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: modes.dirMode })
} }
/** /**

@ -1002,13 +1002,12 @@ describe('Persistence async', function () {
const getMode = async path => { const getMode = async path => {
const { mode } = await fs.lstat(path) const { mode } = await fs.lstat(path)
const octalString = mode.toString('8') return mode & 0o777
return parseInt(octalString.substring(octalString.length - 4), '8')
} }
const testPermissions = async (db, fileMode, dirMode) => { const testPermissions = async (db, fileMode, dirMode) => {
assert.equal(db.persistence.mode.fileMode, fileMode) assert.equal(db.persistence.modes.fileMode, fileMode)
assert.equal(db.persistence.mode.dirMode, dirMode) assert.equal(db.persistence.modes.dirMode, dirMode)
await db.loadDatabaseAsync() await db.loadDatabaseAsync()
assert.equal(await getMode(db.filename), fileMode) assert.equal(await getMode(db.filename), fileMode)
assert.equal(await getMode(path.dirname(db.filename)), dirMode) assert.equal(await getMode(path.dirname(db.filename)), dirMode)
@ -1034,6 +1033,10 @@ const testPermissions = async (db, fileMode, dirMode) => {
describe('permissions', function () { describe('permissions', function () {
const testDb = 'workspace/permissions/test.db' const testDb = 'workspace/permissions/test.db'
before('check OS', function () {
if (process.platform === 'win32' || process.platform === 'win64') this.skip()
})
beforeEach('cleanup', async () => { beforeEach('cleanup', async () => {
try { try {
await fs.chmod(path.dirname(testDb), 0o755) await fs.chmod(path.dirname(testDb), 0o755)
@ -1059,21 +1062,21 @@ describe('permissions', function () {
it('Setting only fileMode', async () => { it('Setting only fileMode', async () => {
const FILE_MODE = 0o600 const FILE_MODE = 0o600
const DIR_MODE = 0o755 const DIR_MODE = 0o755
const db = new Datastore({ filename: testDb, mode: { fileMode: FILE_MODE } }) const db = new Datastore({ filename: testDb, modes: { fileMode: FILE_MODE } })
await testPermissions(db, FILE_MODE, DIR_MODE) await testPermissions(db, FILE_MODE, DIR_MODE)
}) })
it('Setting only dirMode', async () => { it('Setting only dirMode', async () => {
const FILE_MODE = 0o644 const FILE_MODE = 0o644
const DIR_MODE = 0o700 const DIR_MODE = 0o700
const db = new Datastore({ filename: testDb, mode: { dirMode: DIR_MODE } }) const db = new Datastore({ filename: testDb, modes: { dirMode: DIR_MODE } })
await testPermissions(db, FILE_MODE, DIR_MODE) await testPermissions(db, FILE_MODE, DIR_MODE)
}) })
it('Setting fileMode & dirMode', async () => { it('Setting fileMode & dirMode', async () => {
const FILE_MODE = 0o600 const FILE_MODE = 0o600
const DIR_MODE = 0o700 const DIR_MODE = 0o700
const db = new Datastore({ filename: testDb, mode: { dirMode: DIR_MODE, fileMode: FILE_MODE } }) const db = new Datastore({ filename: testDb, modes: { dirMode: DIR_MODE, fileMode: FILE_MODE } })
await testPermissions(db, FILE_MODE, DIR_MODE) await testPermissions(db, FILE_MODE, DIR_MODE)
}) })
}) })

@ -23,7 +23,7 @@ db.loadDatabase((err: Error | null) => {
}) })
// Type 3: Persistent datastore with automatic loading // Type 3: Persistent datastore with automatic loading
db = new Datastore({ filename: 'path/to/datafile_2', autoload: true }) db = new Datastore({ filename: 'path/to/datafile_2', autoload: true, modes: {fileMode: 0o644, dirMode: 0o755} })
// You can issue commands right away // You can issue commands right away
// Of course you can create multiple datastores if you need several // Of course you can create multiple datastores if you need several

Loading…
Cancel
Save