diff --git a/.github/workflows/sauce.yml b/.github/workflows/sauce.yml index a096fe6..0611680 100644 --- a/.github/workflows/sauce.yml +++ b/.github/workflows/sauce.yml @@ -12,7 +12,7 @@ jobs: - name: Set up node uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 - name: Install run: npm install env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3673ec..3ee8eab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12, 14, 16] + node: [16, 18, 20] name: Node ${{ matrix.node }} steps: - name: Checkout diff --git a/README.md b/README.md index 825168a..11fd6ee 100644 --- a/README.md +++ b/README.md @@ -38,19 +38,6 @@ for await (const [key, value] of db.iterator({ gt: 'a' })) { } ``` -With callbacks: - -```js -db.put('example', { hello: 'world' }, (err) => { - if (err) throw err - - db.get('example', (err, value) => { - if (err) throw err - console.log(value) // { hello: 'world' } - }) -}) -``` - diff --git a/UPGRADING.md b/UPGRADING.md index b2a4d0c..3a852e4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,10 @@ This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [`CHANGELOG`](CHANGELOG.md). +## 2.0.0 + +This release upgrades to `abstract-level` 2.0.0 which adds [hooks](https://github.com/Level/abstract-level#hooks) and drops callbacks, not-found errors and support of Node.js < 16. Please refer to the [upgrade guide of `abstract-level`](https://github.com/Level/abstract-level/blob/v2.0.0/UPGRADING.md). + ## 1.0.0 **Introducing `memory-level`: a fork of [`memdown`](https://github.com/Level/memdown) that removes the need for [`level-mem`](https://github.com/Level/mem), [`levelup`](https://github.com/Level/levelup) and more. It implements the [`abstract-level`](https://github.com/Level/abstract-level) interface instead of [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) and thus has the same API as `level-mem` and `levelup` including encodings, promises and events. In addition, you can now choose to use Uint8Array instead of Buffer. Sublevels are builtin.** diff --git a/index.d.ts b/index.d.ts index 9b54f6a..8cabdab 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,7 @@ import { AbstractLevel, AbstractDatabaseOptions, - AbstractOpenOptions, - NodeCallback + AbstractOpenOptions } from 'abstract-level' /** @@ -22,8 +21,6 @@ export class MemoryLevel open (): Promise open (options: OpenOptions): Promise - open (callback: NodeCallback): void - open (options: OpenOptions, callback: NodeCallback): void } /** diff --git a/index.js b/index.js index ca78884..661c3b8 100644 --- a/index.js +++ b/index.js @@ -62,19 +62,19 @@ class MemoryIterator extends AbstractIterator { this[kInit](db[kTree], options) } - _next (callback) { - if (!this[kIterator].valid) return this.nextTick(callback) + async _next () { + if (!this[kIterator].valid) return undefined const key = this[kIterator].key const value = this[kIterator].value - if (!this[kTest](key)) return this.nextTick(callback) + if (!this[kTest](key)) return undefined this[kIterator][this[kAdvance]]() - this.nextTick(callback, null, key, value) + return [key, value] } - _nextv (size, options, callback) { + async _nextv (size, options) { const it = this[kIterator] const entries = [] @@ -83,10 +83,10 @@ class MemoryIterator extends AbstractIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, entries) + return entries } - _all (options, callback) { + async _all (options) { const size = this.limit - this.count const it = this[kIterator] const entries = [] @@ -96,7 +96,7 @@ class MemoryIterator extends AbstractIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, entries) + return entries } } @@ -106,17 +106,17 @@ class MemoryKeyIterator extends AbstractKeyIterator { this[kInit](db[kTree], options) } - _next (callback) { - if (!this[kIterator].valid) return this.nextTick(callback) + async _next () { + if (!this[kIterator].valid) return undefined const key = this[kIterator].key - if (!this[kTest](key)) return this.nextTick(callback) + if (!this[kTest](key)) return undefined this[kIterator][this[kAdvance]]() - this.nextTick(callback, null, key) + return key } - _nextv (size, options, callback) { + async _nextv (size, options) { const it = this[kIterator] const keys = [] @@ -125,10 +125,10 @@ class MemoryKeyIterator extends AbstractKeyIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, keys) + return keys } - _all (options, callback) { + async _all (options) { const size = this.limit - this.count const it = this[kIterator] const keys = [] @@ -138,7 +138,7 @@ class MemoryKeyIterator extends AbstractKeyIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, keys) + return keys } } @@ -148,19 +148,19 @@ class MemoryValueIterator extends AbstractValueIterator { this[kInit](db[kTree], options) } - _next (callback) { - if (!this[kIterator].valid) return this.nextTick(callback) + async _next (options) { + if (!this[kIterator].valid) return undefined const key = this[kIterator].key const value = this[kIterator].value - if (!this[kTest](key)) return this.nextTick(callback) + if (!this[kTest](key)) return undefined this[kIterator][this[kAdvance]]() - this.nextTick(callback, null, value) + return value } - _nextv (size, options, callback) { + async _nextv (size, options) { const it = this[kIterator] const values = [] @@ -169,10 +169,10 @@ class MemoryValueIterator extends AbstractValueIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, values) + return values } - _all (options, callback) { + async _all (options) { const size = this.limit - this.count const it = this[kIterator] const values = [] @@ -182,7 +182,7 @@ class MemoryValueIterator extends AbstractValueIterator { it[this[kAdvance]]() } - this.nextTick(callback, null, values) + return values } } @@ -270,6 +270,7 @@ class MemoryLevel extends AbstractLevel { } // To help migrating from level-mem to abstract-level + // TODO (v2): remove if (typeof location === 'function' || typeof options === 'function' || typeof _ === 'function') { throw new ModuleError('The levelup-style callback argument has been removed', { code: 'LEVEL_LEGACY' @@ -291,13 +292,17 @@ class MemoryLevel extends AbstractLevel { permanence: false, createIfMissing: false, errorIfExists: false, - encodings: { [storeEncoding]: true } + encodings: { [storeEncoding]: true }, + signals: { + // Would have no value here because the operations are synchronous + iterators: false + } }, forward) this[kTree] = createRBT(compare) } - _put (key, value, options, callback) { + async _put (key, value, options) { const it = this[kTree].find(key) if (it.valid) { @@ -305,31 +310,22 @@ class MemoryLevel extends AbstractLevel { } else { this[kTree] = this[kTree].insert(key, value) } - - this.nextTick(callback) } - _get (key, options, callback) { - const value = this[kTree].get(key) - - if (typeof value === 'undefined') { - // TODO: use error code (not urgent, abstract-level normalizes this) - return this.nextTick(callback, new Error('NotFound')) - } - - this.nextTick(callback, null, value) + async _get (key, options) { + // Is undefined if not found + return this[kTree].get(key) } - _getMany (keys, options, callback) { - this.nextTick(callback, null, keys.map(key => this[kTree].get(key))) + async _getMany (keys, options) { + return keys.map(key => this[kTree].get(key)) } - _del (key, options, callback) { + async _del (key, options) { this[kTree] = this[kTree].remove(key) - this.nextTick(callback) } - _batch (operations, options, callback) { + async _batch (operations, options) { let tree = this[kTree] for (const op of operations) { @@ -344,14 +340,13 @@ class MemoryLevel extends AbstractLevel { } this[kTree] = tree - this.nextTick(callback) } - _clear (options, callback) { + async _clear (options) { if (options.limit === -1 && !Object.keys(options).some(isRangeOption)) { // Delete everything by creating a new empty tree. this[kTree] = createRBT(compare) - return this.nextTick(callback) + return } const iterator = this._keys({ ...options }) @@ -359,12 +354,12 @@ class MemoryLevel extends AbstractLevel { let count = 0 - const loop = () => { + while (true) { // TODO: add option to control "batch size" for (let i = 0; i < 500; i++) { - if (++count > limit) return callback() - if (!iterator[kIterator].valid) return callback() - if (!iterator[kTest](iterator[kIterator].key)) return callback() + if (++count > limit) return + if (!iterator[kIterator].valid) return + if (!iterator[kTest](iterator[kIterator].key)) return // Must also include changes made in parallel to clear() this[kTree] = this[kTree].remove(iterator[kIterator].key) @@ -372,10 +367,8 @@ class MemoryLevel extends AbstractLevel { } // Some time to breathe - this.nextTick(loop) + await breathe() } - - this.nextTick(loop) } _iterator (options) { @@ -393,18 +386,17 @@ class MemoryLevel extends AbstractLevel { exports.MemoryLevel = MemoryLevel -// Use setImmediate() in Node.js to allow IO in between our callbacks +let breathe + +// Use setImmediate() in Node.js to allow IO in between work if (typeof process !== 'undefined' && !process.browser && typeof global !== 'undefined' && typeof global.setImmediate === 'function') { const setImmediate = global.setImmediate - // Automatically applies to iterators, sublevels and chained batches as well - MemoryLevel.prototype.nextTick = function (fn, ...args) { - if (args.length === 0) { - setImmediate(fn) - } else { - setImmediate(() => fn(...args)) - } + breathe = function () { + return new Promise(setImmediate) } +} else { + breathe = async function () {} } function isRangeOption (k) { diff --git a/package.json b/package.json index b004ce9..1af1700 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "main": "index.js", "types": "./index.d.ts", "scripts": { - "test": "standard && hallmark && (nyc -s node test.js | faucet) && nyc report", + "test": "standard && hallmark && (nyc -s node test.js | tap-arc) && nyc report", + "test-pessimistic": "node test.js | tap-arc -pv", "test-browsers": "airtap --coverage --verbose test.js", "test-browsers-local": "airtap --coverage -p local test.js", "coverage": "nyc report -r lcovonly" @@ -19,22 +20,22 @@ "CHANGELOG.md" ], "dependencies": { - "abstract-level": "^1.0.0", + "abstract-level": "^2.0.1", "functional-red-black-tree": "^1.0.1", "module-error": "^1.0.1" }, "devDependencies": { - "@voxpelli/tsconfig": "^4.0.0", + "@voxpelli/tsconfig": "^15.0.0", "airtap": "^4.0.3", "airtap-playwright": "^1.0.1", "airtap-sauce": "^1.1.0", "buffer": "^6.0.3", - "faucet": "^0.0.3", "hallmark": "^4.0.0", "nyc": "^15.1.0", "standard": "^17.0.0", + "tap-arc": "^0.3.5", "tape": "^5.0.1", - "typescript": "^4.5.5" + "typescript": "^5.6.3" }, "repository": { "type": "git", @@ -49,6 +50,6 @@ "memory" ], "engines": { - "node": ">=12" + "node": ">=16" } } diff --git a/test.js b/test.js index 31cb463..e38ccdb 100644 --- a/test.js +++ b/test.js @@ -22,24 +22,16 @@ for (const storeEncoding of ['view', 'utf8']) { } // Additional tests for this implementation of abstract-level -test('iterator does not clone buffers', function (t) { +test('iterator does not clone buffers', async function (t) { const db = new MemoryLevel({ keyEncoding: 'buffer', valueEncoding: 'buffer' }) const buf = Buffer.from('a') - db.open(function (err) { - t.ifError(err, 'no open() error') + await db.open() + await db.put(buf, buf) - db.put(buf, buf, function (err) { - t.ifError(err, 'no put() error') - - db.iterator().all(function (err, entries) { - t.ifError(err, 'no all() error') - t.is(entries[0][0], buf, 'key is same buffer') - t.is(entries[0][1], buf, 'value is same buffer') - t.end() - }) - }) - }) + const entries = await db.iterator().all() + t.is(entries[0][0], buf, 'key is same buffer') + t.is(entries[0][1], buf, 'value is same buffer') }) test('throws on unsupported storeEncoding', function (t) { diff --git a/tsconfig.json b/tsconfig.json index cb87215..6c3b40a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@voxpelli/tsconfig/node12.json", + "extends": "@voxpelli/tsconfig/node16.json", "compilerOptions": { "checkJs": false },