Skip to content

Commit

Permalink
Implement Symbol.asyncDispose
Browse files Browse the repository at this point in the history
Per https://github.com/tc39/proposal-explicit-resource-management
which in TypeScript (5.2) allows you to skip calling `.close()`:

```ts
await db.put('example', 'before')
await using snapshot = db.snapshot()
await db.put('example', 'after')
await db.get('example', { snapshot })) // Returns 'before'
```

Works on databases, iterators, snapshots and batches.

Category: addition
  • Loading branch information
vweevers committed Dec 27, 2024
1 parent f81d348 commit 7e7ad5d
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 4 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ const xyz = db.sublevel<string, any>('xyz', { valueEncoding: 'json' })

</details>

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:

<details><summary>Using example</summary>

```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()
}
```

</details>

## Install

With [npm](https://npmjs.org) do:
Expand Down
6 changes: 6 additions & 0 deletions abstract-chained-batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions abstract-iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions abstract-level.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down
6 changes: 6 additions & 0 deletions abstract-snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions test/chained-batch-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
14 changes: 14 additions & 0 deletions test/iterator-explicit-snapshot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,27 @@ 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)
exports.getMany(test, testCommon)
exports.iterator(test, testCommon)
exports.clear(test, testCommon)
exports.cleanup(test, testCommon)
exports.dispose(test, testCommon)
}

function testFactory (test, testCommon) {
Expand Down
14 changes: 14 additions & 0 deletions test/iterator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -597,11 +597,25 @@ 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)
exports.sequence(test, testCommon)
exports.iterator(test, testCommon)
exports.decode(test, testCommon)
exports.tearDown(test, testCommon)
exports.dispose(test, testCommon)
}
8 changes: 8 additions & 0 deletions test/open-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion types/abstract-chained-batch.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Transcoder from 'level-transcoder'
import { AbstractSublevel } from './abstract-sublevel'

export class AbstractChainedBatch<TDatabase, KDefault, VDefault> {
export class AbstractChainedBatch<TDatabase, KDefault, VDefault>
implements AsyncDisposable {
constructor (db: TDatabase)

/**
Expand Down Expand Up @@ -53,6 +54,11 @@ export class AbstractChainedBatch<TDatabase, KDefault, VDefault> {
* committing it.
*/
close (): Promise<void>

/**
* Close the batch.
*/
[Symbol.asyncDispose](): Promise<void>
}

/**
Expand Down
7 changes: 6 additions & 1 deletion types/abstract-iterator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface AbstractValueIteratorOptions<K, V> extends RangeOptions<K>, 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<TDatabase, T> {
declare class CommonIterator<TDatabase, T> implements AsyncDisposable {
/**
* A reference to the database that created this iterator.
*/
Expand All @@ -87,6 +87,11 @@ declare class CommonIterator<TDatabase, T> {
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
*/
close (): Promise<void>

/**
* Close the iterator.
*/
[Symbol.asyncDispose](): Promise<void>
}

export class AbstractIterator<TDatabase, K, V> extends CommonIterator<TDatabase, [K, V]> {
Expand Down
7 changes: 6 additions & 1 deletion types/abstract-level.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { RangeOptions } from './interfaces'
* @template VDefault The default type of values if not overridden on operations.
*/
declare class AbstractLevel<TFormat, KDefault = string, VDefault = string>
extends EventEmitter {
extends EventEmitter implements AsyncDisposable {
/**
* Private database constructor.
*
Expand Down Expand Up @@ -69,6 +69,11 @@ declare class AbstractLevel<TFormat, KDefault = string, VDefault = string>
*/
close (): Promise<void>

/**
* Close the database.
*/
[Symbol.asyncDispose](): Promise<void>

/**
* Get a value from the database by {@link key}.
*/
Expand Down
7 changes: 6 additions & 1 deletion types/abstract-snapshot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,4 +20,9 @@ export class AbstractSnapshot {
* Close the snapshot.
*/
close (): Promise<void>

/**
* Close the snapshot.
*/
[Symbol.asyncDispose](): Promise<void>
}

0 comments on commit 7e7ad5d

Please sign in to comment.