diff --git a/README.md b/README.md index e563c4f..527b2aa 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,33 @@ const xyz = db.sublevel('xyz', { valueEncoding: 'json' }) +TypeScript users can benefit from the `using` keyword because `abstract-level` implements [`Symbol.asyncDispose`](https://github.com/tc39/proposal-explicit-resource-management) on its resources. For example: + +
Using example + +```ts +await db.put('example', 'before') +await using snapshot = db.snapshot() +await db.put('example', 'after') +await db.get('example', { snapshot })) // Returns 'before' +``` + +The equivalent in JavaScript would be: + +```js +await db.put('example', 'before') +const snapshot = db.snapshot() + +try { + await db.put('example', 'after') + await db.get('example', { snapshot })) // Returns 'before' +} finally { + await snapshot.close() +} +``` + +
+ ## Install With [npm](https://npmjs.org) do: diff --git a/abstract-chained-batch.js b/abstract-chained-batch.js index 415af16..eb2df4e 100644 --- a/abstract-chained-batch.js +++ b/abstract-chained-batch.js @@ -358,6 +358,12 @@ class AbstractChainedBatch { async _close () {} } +if (typeof Symbol.asyncDispose === 'symbol') { + AbstractChainedBatch.prototype[Symbol.asyncDispose] = async function () { + return this.close() + } +} + const prepareClose = function (batch) { let close diff --git a/abstract-iterator.js b/abstract-iterator.js index fcac462..c2e2bcc 100644 --- a/abstract-iterator.js +++ b/abstract-iterator.js @@ -274,6 +274,12 @@ class CommonIterator { } } +if (typeof Symbol.asyncDispose === 'symbol') { + CommonIterator.prototype[Symbol.asyncDispose] = async function () { + return this.close() + } +} + // For backwards compatibility this class is not (yet) called AbstractEntryIterator. class AbstractIterator extends CommonIterator { constructor (db, options) { diff --git a/abstract-level.js b/abstract-level.js index f219440..1f37715 100644 --- a/abstract-level.js +++ b/abstract-level.js @@ -945,6 +945,12 @@ const { AbstractSublevel } = require('./lib/abstract-sublevel')({ AbstractLevel exports.AbstractLevel = AbstractLevel exports.AbstractSublevel = AbstractSublevel +if (typeof Symbol.asyncDispose === 'symbol') { + AbstractLevel.prototype[Symbol.asyncDispose] = async function () { + return this.close() + } +} + const assertOpen = function (db) { if (db[kStatus] !== 'open') { throw new ModuleError('Database is not open', { diff --git a/abstract-snapshot.js b/abstract-snapshot.js index 2ca83bc..4ac9c09 100644 --- a/abstract-snapshot.js +++ b/abstract-snapshot.js @@ -70,6 +70,12 @@ class AbstractSnapshot { async _close () {} } +if (typeof Symbol.asyncDispose === 'symbol') { + AbstractSnapshot.prototype[Symbol.asyncDispose] = async function () { + return this.close() + } +} + const privateClose = async function (snapshot, owner) { await snapshot._close() owner.detachResource(snapshot) diff --git a/test/chained-batch-test.js b/test/chained-batch-test.js index 29bebff..91201a0 100644 --- a/test/chained-batch-test.js +++ b/test/chained-batch-test.js @@ -308,10 +308,24 @@ exports.tearDown = function (test, testCommon) { }) } +exports.dispose = function (test, testCommon) { + // Can't use the syntax yet (https://github.com/tc39/proposal-explicit-resource-management) + Symbol.asyncDispose && test('Symbol.asyncDispose', async function (t) { + const db = testCommon.factory() + await db.open() + + const batch = db.batch() + await batch[Symbol.asyncDispose]() + + return db.close() + }) +} + exports.all = function (test, testCommon) { exports.setUp(test, testCommon) exports.args(test, testCommon) exports.batch(test, testCommon) exports.events(test, testCommon) exports.tearDown(test, testCommon) + exports.dispose(test, testCommon) } diff --git a/test/iterator-explicit-snapshot-test.js b/test/iterator-explicit-snapshot-test.js index d0f14f7..e8a51ea 100644 --- a/test/iterator-explicit-snapshot-test.js +++ b/test/iterator-explicit-snapshot-test.js @@ -303,6 +303,19 @@ exports.cleanup = function (test, testCommon) { }) } +exports.dispose = function (test, testCommon) { + // Can't use the syntax yet (https://github.com/tc39/proposal-explicit-resource-management) + Symbol.asyncDispose && test('Symbol.asyncDispose', async function (t) { + const db = testCommon.factory() + await db.open() + + const snapshot = db.snapshot() + await snapshot[Symbol.asyncDispose]() + + return db.close() + }) +} + exports.all = function (test, testCommon) { exports.traits(test, testCommon) exports.get(test, testCommon) @@ -310,6 +323,7 @@ exports.all = function (test, testCommon) { exports.iterator(test, testCommon) exports.clear(test, testCommon) exports.cleanup(test, testCommon) + exports.dispose(test, testCommon) } function testFactory (test, testCommon) { diff --git a/test/iterator-test.js b/test/iterator-test.js index 22430b3..d66c791 100644 --- a/test/iterator-test.js +++ b/test/iterator-test.js @@ -597,6 +597,19 @@ exports.tearDown = function (test, testCommon) { }) } +exports.dispose = function (test, testCommon) { + // Can't use the syntax yet (https://github.com/tc39/proposal-explicit-resource-management) + Symbol.asyncDispose && test('Symbol.asyncDispose', async function (t) { + const db = testCommon.factory() + await db.open() + + const iterator = db.iterator() + await iterator[Symbol.asyncDispose]() + + return db.close() + }) +} + exports.all = function (test, testCommon) { exports.setUp(test, testCommon) exports.args(test, testCommon) @@ -604,4 +617,5 @@ exports.all = function (test, testCommon) { exports.iterator(test, testCommon) exports.decode(test, testCommon) exports.tearDown(test, testCommon) + exports.dispose(test, testCommon) } diff --git a/test/open-test.js b/test/open-test.js index b4f81b6..87fb66d 100644 --- a/test/open-test.js +++ b/test/open-test.js @@ -248,6 +248,14 @@ exports.open = function (test, testCommon) { await new Promise((resolve) => db.once('open', resolve)) return db.close() }) + + // Can't use the syntax yet (https://github.com/tc39/proposal-explicit-resource-management) + Symbol.asyncDispose && test('Symbol.asyncDispose', async function (t) { + const db = testCommon.factory() + await db.open() + await db[Symbol.asyncDispose]() + t.is(db.status, 'closed') + }) } exports.all = function (test, testCommon) { diff --git a/types/abstract-chained-batch.d.ts b/types/abstract-chained-batch.d.ts index 8d46ebc..ae6c8f6 100644 --- a/types/abstract-chained-batch.d.ts +++ b/types/abstract-chained-batch.d.ts @@ -1,7 +1,8 @@ import * as Transcoder from 'level-transcoder' import { AbstractSublevel } from './abstract-sublevel' -export class AbstractChainedBatch { +export class AbstractChainedBatch + implements AsyncDisposable { constructor (db: TDatabase) /** @@ -53,6 +54,11 @@ export class AbstractChainedBatch { * committing it. */ close (): Promise + + /** + * Close the batch. + */ + [Symbol.asyncDispose](): Promise } /** diff --git a/types/abstract-iterator.d.ts b/types/abstract-iterator.d.ts index a31ff59..e8c5eb6 100644 --- a/types/abstract-iterator.d.ts +++ b/types/abstract-iterator.d.ts @@ -60,7 +60,7 @@ export interface AbstractValueIteratorOptions extends RangeOptions, Com * @template TDatabase Type of the database that created this iterator. * @template T Type of items yielded. Items can be entries, keys or values. */ -declare class CommonIterator { +declare class CommonIterator implements AsyncDisposable { /** * A reference to the database that created this iterator. */ @@ -87,6 +87,11 @@ declare class CommonIterator { * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of */ close (): Promise + + /** + * Close the iterator. + */ + [Symbol.asyncDispose](): Promise } export class AbstractIterator extends CommonIterator { diff --git a/types/abstract-level.d.ts b/types/abstract-level.d.ts index d3232e5..ad22fce 100644 --- a/types/abstract-level.d.ts +++ b/types/abstract-level.d.ts @@ -24,7 +24,7 @@ import { RangeOptions } from './interfaces' * @template VDefault The default type of values if not overridden on operations. */ declare class AbstractLevel - extends EventEmitter { + extends EventEmitter implements AsyncDisposable { /** * Private database constructor. * @@ -69,6 +69,11 @@ declare class AbstractLevel */ close (): Promise + /** + * Close the database. + */ + [Symbol.asyncDispose](): Promise + /** * Get a value from the database by {@link key}. */ diff --git a/types/abstract-snapshot.d.ts b/types/abstract-snapshot.d.ts index 68f95b7..71299dc 100644 --- a/types/abstract-snapshot.d.ts +++ b/types/abstract-snapshot.d.ts @@ -2,7 +2,7 @@ * A lightweight token that represents a version of a database at a particular point in * time. */ -export class AbstractSnapshot { +export class AbstractSnapshot implements AsyncDisposable { /** * Increment reference count, to register work that should delay closing until * {@link unref} is called an equal amount of times. The promise that will be returned @@ -20,4 +20,9 @@ export class AbstractSnapshot { * Close the snapshot. */ close (): Promise + + /** + * Close the snapshot. + */ + [Symbol.asyncDispose](): Promise }