From fcd15462207f390968a9a52fa2e1c294c257aa72 Mon Sep 17 00:00:00 2001 From: mehdi Date: Wed, 20 Oct 2021 00:44:08 +0200 Subject: [PATCH] add typings --- .github/workflows/github-actions-demo.yml | 17 +- index.d.ts | 117 +++++++ lib/datastore.js | 43 +-- package-lock.json | 268 +++++++++++++++ package.json | 9 +- typings-tests.ts | 386 ++++++++++++++++++++++ 6 files changed, 814 insertions(+), 26 deletions(-) create mode 100644 index.d.ts create mode 100644 typings-tests.ts diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index 8d5b3a0..abbf719 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -3,13 +3,24 @@ name: Tests on: [push, pull_request] jobs: - node-tests: + quality: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Node.js 14 + uses: actions/setup-node@v2 + with: + node-version: 14.x + cache: 'npm' + - run: npm ci + - run: npm run lint + - run: npm run test:typings + node-tests: + runs-on: ubuntu-latest strategy: matrix: node-version: [12.x, 14.x, 16.x] - steps: - uses: actions/checkout@v2 - name: Install Node.js ${{ matrix.node-version }} @@ -18,7 +29,6 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - - run: npm run lint - run: npm run test browser-tests: @@ -34,5 +44,4 @@ jobs: - name: Install Chrome Stable uses: browser-actions/setup-chrome@latest - run: npm ci - - run: npm run lint - run: npm run test:browser diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2b76bd0 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,117 @@ +// Type definitions for @seald-io/nedb 2.1.0 +// Project: https://github.com/seald/nedb forked from https://github.com/louischatriot/nedb +// Definitions by: Mehdi Kouhen +// Stefan Steinhart +// Anthony Nichols +// Alejandro Fernandez Haro +// TypeScript Version: 4.4 + +/// + +import { EventEmitter } from 'events'; + +export default Nedb; + +declare class Nedb extends EventEmitter { + constructor(pathOrOptions?: string | Nedb.DataStoreOptions); + + persistence: Nedb.Persistence; + + loadDatabase(): void; + + getAllData(): T[]; + + resetIndexes(newData?: any): void; + + ensureIndex(options: Nedb.EnsureIndexOptions, callback?: (err: Error | null) => void): void; + + removeIndex(fieldName: string, callback?: (err: Error | null) => void): void; + + addToIndexes(doc: T | T[]): void; + + removeFromIndexes(doc: T | T[]): void; + + updateIndexes(oldDoc: T, newDoc: T): void; + updateIndexes(updates: Array<{ oldDoc: T; newDoc: T }>): void; + + getCandidates(query: any, dontExpireStaleDocs: boolean, callback?: (err: Error | null, candidates: T[]) => void): void; + + insert(newDoc: T, callback?: (err: Error | null, document: T) => void): void; + insert(newDocs: T[], callback?: (err: Error | null, documents: T[]) => void): void; + + count(query: any, callback: (err: Error | null, n: number) => void): void; + count(query: any): Nedb.CursorCount; + + find(query: any, projection: any, callback?: (err: Error | null, documents: T[]) => void): void; + find(query: any, projection?: any): Nedb.Cursor; + find(query: any, callback: (err: Error | null, documents: T[]) => void): void; + + findOne(query: any, projection: any, callback: (err: Error | null, document: T) => void): void; + findOne(query: any, callback: (err: Error | null, document: T) => void): void; + + update(query: any, updateQuery: any, options?: Nedb.UpdateOptions, callback?: (err: Error | null, numberOfUpdated: number, affectedDocuments: T | T[] | null, upsert: boolean | null) => void): void; + + remove(query: any, options: Nedb.RemoveOptions, callback?: (err: Error | null, n: number) => void): void; + remove(query: any, callback?: (err: Error | null, n: number) => void): void; + + addListener(event: 'compaction.done', listener: () => void): this; + on(event: 'compaction.done', listener: () => void): this; + once(event: 'compaction.done', listener: () => void): this; + prependListener(event: 'compaction.done', listener: () => void): this; + prependOnceListener(event: 'compaction.done', listener: () => void): this; + removeListener(event: 'compaction.done', listener: () => void): this; + off(event: 'compaction.done', listener: () => void): this; + listeners(event: 'compaction.done'): Array<() => void>; + rawListeners(event: 'compaction.done'): Array<() => void>; + listenerCount(type: 'compaction.done'): number; +} + +declare namespace Nedb { + interface Cursor { + sort(query: any): Cursor; + skip(n: number): Cursor; + limit(n: number): Cursor; + projection(query: any): Cursor; + exec(callback: (err: Error | null, documents: T[]) => void): void; + } + + interface CursorCount { + exec(callback: (err: Error | null, count: number) => void): void; + } + + interface DataStoreOptions { + filename?: string; + timestampData?: boolean; + inMemoryOnly?: boolean; + nodeWebkitAppName?: boolean; + autoload?: boolean; + onload?(error: Error | null): any; + beforeDeserialization?(line: string): string; + afterSerialization?(line: string): string; + corruptAlertThreshold?: number; + compareStrings?(a: string, b: string): number; + } + + interface UpdateOptions { + multi?: boolean; + upsert?: boolean; + returnUpdatedDocs?: boolean; + } + + interface RemoveOptions { + multi?: boolean; + } + + interface EnsureIndexOptions { + fieldName: string; + unique?: boolean; + sparse?: boolean; + expireAfterSeconds?: number; + } + + interface Persistence { + compactDatafile(): void; + setAutocompactionInterval(interval: number): void; + stopAutocompaction(): void; + } +} diff --git a/lib/datastore.js b/lib/datastore.js index a28bf58..16c4d19 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -14,8 +14,7 @@ class Datastore extends EventEmitter { * @param {String} [options.filename] Optional, datastore will be in-memory only if not provided * @param {Boolean} [options.timestampData] Optional, defaults to false. If set to true, createdAt and updatedAt will be created and populated automatically (if not specified by user) * @param {Boolean} [options.inMemoryOnly] Optional, defaults to false - * @param {String} [options.nodeWebkitAppName] Optional, specify the name of your NW app if you want options.filename to be relative to the directory where - * Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion) + * @param {String} [options.nodeWebkitAppName] Optional, specify the name of your NW app if you want options.filename to be relative to the directory where Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion) * @param {Boolean} [options.autoload] Optional, defaults to false * @param {Function} [options.onload] Optional, if autoload is used this will be called after the load database with the error object as parameter. If you don't pass it the error will be thrown * @param {Function} [options.beforeDeserialization] Optional, serialization hooks @@ -112,9 +111,9 @@ class Datastore extends EventEmitter { * We use an async API for consistency with the rest of the code * @param {Object} options * @param {String} options.fieldName - * @param {Boolean} options.unique - * @param {Boolean} options.sparse - * @param {Number} options.expireAfterSeconds - Optional, if set this index becomes a TTL index (only works on Date fields, not arrays of Date) + * @param {Boolean} [options.unique] + * @param {Boolean} [options.sparse] + * @param {Number} [options.expireAfterSeconds] - Optional, if set this index becomes a TTL index (only works on Date fields, not arrays of Date) * @param {Function} callback Optional callback, signature: err */ ensureIndex (options = {}, callback = () => {}) { @@ -321,16 +320,17 @@ class Datastore extends EventEmitter { /** * Insert a new document + * Private Use Datastore.insert which has the same signature * @param {Document} newDoc * @param {Function} callback Optional callback, signature: err, insertedDoc * - * @api private Use Datastore.insert which has the same signature + * @private */ _insert (newDoc, callback = () => {}) { let preparedDoc try { - preparedDoc = this.prepareDocumentForInsertion(newDoc) + preparedDoc = this._prepareDocumentForInsertion(newDoc) this._insertInCache(preparedDoc) } catch (e) { return callback(e) @@ -344,28 +344,29 @@ class Datastore extends EventEmitter { /** * Create a new _id that's not already in use + * @private */ - createNewId () { + _createNewId () { let attemptId = customUtils.uid(16) // Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is extremely small, so this is O(1) - if (this.indexes._id.getMatching(attemptId).length > 0) attemptId = this.createNewId() + if (this.indexes._id.getMatching(attemptId).length > 0) attemptId = this._createNewId() return attemptId } /** * Prepare a document (or array of documents) to be inserted in a database * Meaning adds _id and timestamps if necessary on a copy of newDoc to avoid any side effect on user input - * @api private + * @private */ - prepareDocumentForInsertion (newDoc) { + _prepareDocumentForInsertion (newDoc) { let preparedDoc if (Array.isArray(newDoc)) { preparedDoc = [] - newDoc.forEach(doc => { preparedDoc.push(this.prepareDocumentForInsertion(doc)) }) + newDoc.forEach(doc => { preparedDoc.push(this._prepareDocumentForInsertion(doc)) }) } else { preparedDoc = model.deepCopy(newDoc) - if (preparedDoc._id === undefined) preparedDoc._id = this.createNewId() + if (preparedDoc._id === undefined) preparedDoc._id = this._createNewId() const now = new Date() if (this.timestampData && preparedDoc.createdAt === undefined) preparedDoc.createdAt = now if (this.timestampData && preparedDoc.updatedAt === undefined) preparedDoc.updatedAt = now @@ -377,7 +378,7 @@ class Datastore extends EventEmitter { /** * If newDoc is an array of documents, this will insert all documents in the cache - * @api private + * @private */ _insertInCache (preparedDoc) { if (Array.isArray(preparedDoc)) this._insertMultipleDocsInCache(preparedDoc) @@ -387,7 +388,7 @@ class Datastore extends EventEmitter { /** * If one insertion fails (e.g. because of a unique constraint), roll back all previous * inserts and throws the error - * @api private + * @private */ _insertMultipleDocsInCache (preparedDocs) { let failingIndex @@ -418,7 +419,7 @@ class Datastore extends EventEmitter { /** * Count all documents matching the query - * @param {Object} query MongoDB-style query + * @param {Query} query MongoDB-style query * @param {Function} callback Optional callback, signature: err, count */ count (query, callback) { @@ -491,7 +492,8 @@ class Datastore extends EventEmitter { } /** - * Update all docs matching query + * Update all docs matching query. + * Use Datastore.update which has the same signature * @param {Object} query * @param {Object} updateQuery * @param {Object} options Optional options @@ -513,7 +515,7 @@ class Datastore extends EventEmitter { * user to check whether an upsert had occured: checking the type of affectedDocuments or running another find query on * the whole dataset to check its size. Both options being ugly, the breaking change was necessary. * - * @api private Use Datastore.update which has the same signature + * @private */ _update (query, updateQuery, options, cb) { if (typeof options === 'function') { @@ -614,14 +616,15 @@ class Datastore extends EventEmitter { } /** - * Remove all docs matching the query + * Remove all docs matching the query. + * Use Datastore.remove which has the same signature * For now very naive implementation (similar to update) * @param {Object} query * @param {Object} options Optional options * options.multi If true, can update multiple documents (defaults to false) * @param {Function} cb Optional callback, signature: err, numRemoved * - * @api private Use Datastore.remove which has the same signature + * @private */ _remove (query, options, cb) { if (typeof options === 'function') { diff --git a/package-lock.json b/package-lock.json index ad256d2..c520063 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "standard": "^16.0.3", "terser-webpack-plugin": "^5.1.2", "timers-browserify": "^2.0.12", + "ts-node": "^10.3.0", "webpack": "^5.37.0", "webpack-cli": "^4.7.0", "xvfb-maybe": "^0.2.1" @@ -135,6 +136,27 @@ "node": ">=4" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", @@ -192,6 +214,30 @@ "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", "integrity": "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "node_modules/@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -494,6 +540,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -565,6 +620,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1110,6 +1171,12 @@ "node": ">= 0.10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3163,6 +3230,12 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -4970,6 +5043,68 @@ "node": ">=0.6" } }, + "node_modules/ts-node": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.0.tgz", + "integrity": "sha512-RYIy3i8IgpFH45AX4fQHExrT8BxDeKTdC83QFJkNzkvt8uFB6QJ8XMyhynYiKMLxt9a7yuXaDBZNOYS3XjDcYw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", @@ -5025,6 +5160,20 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/ua-parser-js": { "version": "0.7.28", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", @@ -5600,6 +5749,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5698,6 +5856,21 @@ } } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", @@ -5748,6 +5921,30 @@ "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", "integrity": "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==" }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -6026,6 +6223,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6076,6 +6279,12 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6499,6 +6708,12 @@ "vary": "^1" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -8046,6 +8261,12 @@ "yallist": "^4.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -9389,6 +9610,40 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "ts-node": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.0.tgz", + "integrity": "sha512-RYIy3i8IgpFH45AX4fQHExrT8BxDeKTdC83QFJkNzkvt8uFB6QJ8XMyhynYiKMLxt9a7yuXaDBZNOYS3XjDcYw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", @@ -9432,6 +9687,13 @@ "mime-types": "~2.1.24" } }, + "typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "dev": true, + "peer": true + }, "ua-parser-js": { "version": "0.7.28", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", @@ -9853,6 +10115,12 @@ "is-plain-obj": "^2.1.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 0ccea36..a49062a 100755 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "files": [ "lib/**/*.js", "browser-version/**/*.js", - "index.js" + "index.js", + "index.d.ts" ], + "types": "index.d.ts", "author": { "name": "Timothée Rebours", "email": "tim@seald.io", @@ -63,6 +65,7 @@ "standard": "^16.0.3", "terser-webpack-plugin": "^5.1.2", "timers-browserify": "^2.0.12", + "ts-node": "^10.3.0", "webpack": "^5.37.0", "webpack-cli": "^4.7.0", "xvfb-maybe": "^0.2.1" @@ -73,6 +76,7 @@ "build:browser": "webpack && webpack --optimization-minimize", "pretest:browser": "npm run build:browser", "test:browser": "xvfb-maybe karma start karma.conf.local.js", + "test:typings": "ts-node ./typings-tests.ts", "prepublishOnly": "npm run build:browser" }, "main": "index.js", @@ -87,7 +91,8 @@ }, "standard": { "ignore": [ - "browser-version/out" + "browser-version/out", + "**/*.ts" ] } } diff --git a/typings-tests.ts b/typings-tests.ts new file mode 100644 index 0000000..2d22763 --- /dev/null +++ b/typings-tests.ts @@ -0,0 +1,386 @@ +/** + * Created by stefansteinhart on 31.01.15. + * Modified my arantes555 on 19.10.2021. + */ + +import Datastore from './' +import { mkdirSync } from 'fs' + +mkdirSync('./workspace/typings/', { recursive: true }) +process.chdir('./workspace/typings/') + +// Type 1: In-memory only datastore (no need to load the database) +let db = new Datastore() + +// Type 2: Persistent datastore with manual loading +db = new Datastore({ filename: 'path/to/datafile' }) +db.loadDatabase() + +// Type 3: Persistent datastore with automatic loading +db = new Datastore({ filename: 'path/to/datafile', autoload: true }) +// You can issue commands right away + +// Type 4: Persistent datastore for a Node Webkit app called 'nwtest' +// For example on Linux, the datafile will be ~/.config/nwtest/nedb-data/something.db +db = new Datastore({ filename: 'something.db' }) + +// Of course you can create multiple datastores if you need several +// collections. In this case it's usually a good idea to use autoload for all collections. +const dbContainer: any = {} +dbContainer.users = new Datastore('path/to/users.db') +dbContainer.robots = new Datastore('path/to/robots.db') + +// You need to load each database (here we do it asynchronously) +dbContainer.users.loadDatabase() +dbContainer.robots.loadDatabase() + +const doc: any = { + hello: 'world', + n: 5, + today: new Date(), + nedbIsAwesome: true, + notthere: null, + notToBeSaved: undefined, // Will not be saved + fruits: ['apple', 'orange', 'pear'], + infos: { name: 'nedb' } +} + +db.insert(doc, (err: Error | null, newDoc: any) => { // Callback is optional + // newDoc is the newly inserted document, including its _id + // newDoc has no key called notToBeSaved since its value was undefined +}) + +db.insert([{ a: 5 }, { a: 42 }], (err: Error | null, newdocs: any[]) => { + // Two documents were inserted in the database + // newDocs is an array with these documents, augmented with their _id +}) + +// If there is a unique constraint on field 'a', this will fail +db.insert([{ a: 5 }, { a: 42 }, { a: 5 }], (err: Error | null) => { + // err is a 'uniqueViolated' error + // The database was not modified +}) + +// Finding all planets in the solar system +db.find({ system: 'solar' }, (err: Error | null, docs: any[]) => { + // docs is an array containing documents Mars, Earth, Jupiter + // If no document is found, docs is equal to [] +}) + +// Finding all planets whose name contain the substring 'ar' using a regular expression +db.find({ planet: /ar/ }, (err: Error | null, docs: any[]) => { + // docs contains Mars and Earth +}) + +// Finding all inhabited planets in the solar system +db.find({ system: 'solar', inhabited: true }, (err: Error | null, docs: any[]) => { + // docs is an array containing document Earth only +}) + +// Use the dot-notation to match fields in subdocuments +db.find({ 'humans.genders': 2 }, (err: Error | null, docs: any[]) => { + // docs contains Earth +}) + +// Use the dot-notation to navigate arrays of subdocuments +db.find({ 'completeData.planets.name': 'Mars' }, (err: Error | null, docs: any[]) => { + // docs contains document 5 +}) + +db.find({ 'completeData.planets.name': 'Jupiter' }, (err: Error | null, docs: any[]) => { + // docs is empty +}) + +db.find({ 'completeData.planets.0.name': 'Earth' }, (err: Error | null, docs: any[]) => { + // docs contains document 5 + // If we had tested against "Mars" docs would be empty because we are matching against a specific array element +}) + +// You can also deep-compare objects. Don't confuse this with dot-notation! +db.find({ humans: { genders: 2 } }, (err: Error | null, docs: any[]) => { + // docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true } +}) + +// Find all documents in the collection +db.find({}, (err: Error | null, docs: any[]) => { +}) + +// The same rules apply when you want to only find one document +db.findOne({ _id: 'id1' }, (err: Error | null, doc: any) => { + // doc is the document Mars + // If no document is found, doc is null +}) + +// $lt, $lte, $gt and $gte work on numbers and strings +db.find({ 'humans.genders': { $gt: 5 } }, (err: Error | null, docs: any[]) => { + // docs contains Omicron Persei 8, whose humans have more than 5 genders (7). +}) + +// When used with strings, lexicographical order is used +db.find({ planet: { $gt: 'Mercury' } }, (err: Error | null, docs: any[]) => { + // docs contains Omicron Persei 8 +}) + +// Using $in. $nin is used in the same way +db.find({ planet: { $in: ['Earth', 'Jupiter'] } }, (err: Error | null, docs: any[]) => { + // docs contains Earth and Jupiter +}) + +// Using $exists +db.find({ satellites: { $exists: true } }, (err: Error | null, docs: any[]) => { + // docs contains only Mars +}) + +// Using $regex with another operator +db.find({ planet: { $regex: /ar/, $nin: ['Jupiter', 'Earth'] } }, (err: Error | null, docs: any[]) => { + // docs only contains Mars because Earth was excluded from the match by $nin +}) + +// Using an array-specific comparison function +// Note: you can't use nested comparison functions, e.g. { $size: { $lt: 5 } } will throw an error +db.find({ satellites: { $size: 2 } }, (err: Error | null, docs: any[]) => { + // docs contains Mars +}) + +db.find({ satellites: { $size: 1 } }, (err: Error | null, docs: any[]) => { + // docs is empty +}) + +// If a document's field is an array, matching it means matching any element of the array +db.find({ satellites: 'Phobos' }, (err: Error | null, docs: any[]) => { + // docs contains Mars. Result would have been the same if query had been { satellites: 'Deimos' } +}) + +// This also works for queries that use comparison operators +db.find({ satellites: { $lt: 'Amos' } }, (err: Error | null, docs: any[]) => { + // docs is empty since Phobos and Deimos are after Amos in lexicographical order +}) + +// This also works with the $in and $nin operator +db.find({ satellites: { $in: ['Moon', 'Deimos'] } }, (err: Error | null, docs: any[]) => { + // docs contains Mars (the Earth document is not complete!) +}) + +db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }, (err: Error | null, docs: any[]) => { + // docs contains Earth and Mars +}) + +db.find({ $not: { planet: 'Earth' } }, (err: Error | null, docs: any[]) => { + // docs contains Mars, Jupiter, Omicron Persei 8 +}) + +db.find({ + $where () { + return parseInt(Object.keys(this)[0], 10) > 6 + } +}, (err: Error | null, docs: any[]) => { + // docs with more than 6 properties +}) + +// You can mix normal queries, comparison queries and logical operators +db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }], inhabited: true }, (err: Error | null, docs: any[]) => { + // docs contains Earth +}) + +// No query used means all results are returned (before the Cursor modifiers) +db.find({}).sort({ planet: 1 }).skip(1).limit(2).exec((err: Error | null, docs: any[]) => { + // docs is [doc3, doc1] +}) + +// You can sort in reverse order like this +db.find({ system: 'solar' }).sort({ planet: -1 }).exec((err: Error | null, docs: any[]) => { + // docs is [doc1, doc3, doc2] +}) + +// You can sort on one field, then another, and so on like this: +db.find({}).sort({ firstField: 1, secondField: -1 }) + +// Same database as above + +// Keeping only the given fields +db.find({ planet: 'Mars' }, { planet: 1, system: 1 }, (err: Error | null, docs: any[]) => { + // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] +}) + +// Keeping only the given fields but removing _id +db.find({ planet: 'Mars' }, { planet: 1, system: 1, _id: 0 }, (err: Error | null, docs: any[]) => { + // docs is [{ planet: 'Mars', system: 'solar' }] +}) + +// Omitting only the given fields and removing _id +db.find({ planet: 'Mars' }, { planet: 0, system: 0, _id: 0 }, (err: Error | null, docs: any[]) => { + // docs is [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }] +}) + +// Failure: using both modes at the same time +db.find({ planet: 'Mars' }, { planet: 0, system: 1 }, (err: Error | null, docs: any[]) => { + // err is the error message, docs is undefined +}) + +// You can also use it in a Cursor way but this syntax is not compatible with MongoDB +// If upstream compatibility is important don't use this method +db.find({ planet: 'Mars' }).projection({ planet: 1, system: 1 }).exec((err: Error | null, docs: any[]) => { + // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] +}) + +// Count all planets in the solar system +db.count({ system: 'solar' }, (err: Error | null, count: number) => { + // count equals to 3 +}) + +// Count all documents in the datastore +db.count({}, (err: Error | null, count: number) => { + // count equals to 4 +}) + +// Let's use the same example collection as in the "finding document" part +// { _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 } + +// Replace a document by another +db.update({ planet: 'Jupiter' }, { planet: 'Pluton' }, {}, (err: Error | null, numReplaced: number) => { + // numReplaced = 1 + // 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) +}) + +// Set an existing field's value +db.update({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true }, (err: Error | null, numReplaced: number) => { + // numReplaced = 3 + // Field 'system' on Mars, Earth, Jupiter now has value 'solar system' +}) + +// Setting the value of a non-existing field in a subdocument by using the dot-notation +db.update({ planet: 'Mars' }, { $set: { 'data.satellites': 2, 'data.red': true } }, {}, () => { + // 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 + db.update({ 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 + }) +}) + +// Deleting a field +db.update({ 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 +}) + +// Upserting a document +db.update({ planet: 'Pluton' }, { + planet: 'Pluton', + inhabited: false +}, { upsert: true }, (err: Error | null, numReplaced: number) => { + // numReplaced = 1, upsert = { _id: 'id5', planet: 'Pluton', inhabited: false } + // A new document { _id: 'id5', planet: 'Pluton', inhabited: false } has been added to the collection +}) + +// If you upsert with a modifier, the upserted doc is the query modified by the modifier +// This is simpler than it sounds :) +db.update({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true }, () => { + // A new document { _id: 'id5', planet: 'Pluton', distance: 38 } has been added to the collection +}) + +// 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 +db.update({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {}, () => { + // Now the fruits array is ['apple', 'orange', 'pear', 'banana'] +}) + +// $pop removes an element from the end (if used with 1) or the front (if used with -1) of the array +db.update({ _id: 'id6' }, { $pop: { fruits: 1 } }, {}, () => { + // Now the fruits array is ['apple', 'orange'] + // With { $pop: { fruits: -1 } }, it would have been ['orange', 'pear'] +}) + +// $addToSet adds an element to an array only if it isn't already in it +// Equality is deep-checked (i.e. $addToSet will not insert an object in an array already containing the same object) +// Note that it doesn't check whether the array contained duplicates before or not +db.update({ _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 +}) + +// $pull removes all values matching a value or even any NeDB query from the array +db.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, () => { + // Now the fruits array is ['orange', 'pear'] +}) +db.update({ _id: 'id6' }, { $pull: { fruits: { $in: ['apple', 'pear'] } } }, {}, () => { + // Now the fruits array is ['orange'] +}) + +// $each can be used to $push or $addToSet multiple values at once +// This example works the same way with $addToSet +db.update({ _id: 'id6' }, { $push: { fruits: { $each: ['banana', 'orange'] } } }, {}, () => { + // Now the fruits array is ['apple', 'orange', 'pear', 'banana', 'orange'] +}) + +// Let's use the same example collection as in the "finding document" part +// { _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 } + +// Remove one document from the collection +// options set to {} since the default for multi is false +db.remove({ _id: 'id2' }, {}, (err: Error | null, numRemoved: number) => { + // numRemoved = 1 +}) + +// Remove multiple documents +db.remove({ system: 'solar' }, { multi: true }, (err: Error | null, numRemoved: number) => { + // numRemoved = 3 + // All planets from the solar system were removed +}) + +db.ensureIndex({ fieldName: 'somefield' }, (err: Error | null) => { + // If there was an error, err is not null +}) + +// Using a unique constraint with the index +db.ensureIndex({ fieldName: 'somefield', unique: true }, (err: Error | null) => { +}) + +// Using a sparse unique index +db.ensureIndex({ fieldName: 'somefield', unique: true, sparse: true }, (err: Error | null) => { +}) + +// Example of using expireAfterSeconds to remove documents 1 hour +// after their creation (db's timestampData option is true here) +db.ensureIndex({ fieldName: 'somefield', expireAfterSeconds: 3600 }, (err: Error | null) => { +}) + +// Format of the error message when the unique constraint is not met +db.insert({ somefield: 'nedb' }, (err: Error | null) => { + // err is null + db.insert({ somefield: 'nedb' }, (err: Error | null) => { + // err is { errorType: 'uniqueViolated' + // , key: 'name' + // , message: 'Unique constraint violated for key name' } + }) +}) + +// Remove index on field somefield +db.removeIndex('somefield', (err: Error | null) => { +}) + +db.addListener('compaction.done', () => {}) +db.on('compaction.done', () => {}) +db.once('compaction.done', () => {}) +db.prependListener('compaction.done', () => {}) +db.prependOnceListener('compaction.done', () => {}) +db.removeListener('compaction.done', () => {}) +db.off('compaction.done', () => {}) +db.listeners('compaction.done') // $ExpectType (() => void)[] +db.rawListeners('compaction.done') // $ExpectType (() => void)[] +db.listenerCount('compaction.done') // $ExpectType number