Skip to content
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ async function third (instance, opts) {
* <a href="#override"><code>instance.<b>override()</b></code></a>
* <a href="#onClose"><code>instance.<b>onClose()</b></code></a>
* <a href="#close"><code>instance.<b>close()</b></code></a>
* <a href="#symbol-asyncdispose"><code>instance<b>\[Symbol.asyncDispose\]()</b></code></a>
* <a href="#toJSON"><code>avvio.<b>toJSON()</b></code></a>
* <a href="#prettyPrint"><code>avvio.<b>prettyPrint()</b></code></a>

Expand Down Expand Up @@ -581,6 +582,35 @@ app.close()

-------------------------------------------------------

<a name="symbol-asyncdispose"></a>

### app[Symbol.asyncDispose]()

This method is an alias for [`app.close()`](#close) and is used to support the [ECMAScript Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management/) proposal. It allows you to use the `await using` syntax to automatically close the avvio instance when it goes out of scope.

This is especially useful in unit tests and short-lived processes where you need to ensure resources are cleaned up automatically.

Example:

```js
test('my test', async () => {
await using app = avvio()

app.use(function (server, opts, done) {
// Your plugin code
done()
})

await app.ready()

// app.close() will be called automatically when exiting this scope
})
```

**Note:** This feature requires Node.js 20 or later, as `Symbol.asyncDispose` is not available in earlier versions. The implementation includes a runtime check to ensure compatibility with older Node.js versions.

-------------------------------------------------------

<a name="toJSON"></a>

### avvio.toJSON()
Expand Down
14 changes: 14 additions & 0 deletions boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ function Boot (server, opts, done) {

inherits(Boot, EE)

// Older nodejs versions may not have asyncDispose
if ('asyncDispose' in Symbol) {
Boot.prototype[Symbol.asyncDispose] = function () {
return new Promise((resolve, reject) => {
this.close((err) => {
if (err) {
return reject(err)
}
resolve()
})
})
}
}

Boot.prototype.start = function () {
this.started = true

Expand Down
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ declare namespace avvio {

started: boolean;
booted: boolean;

/** Alias for {@linkcode Avvio.close} */
[Symbol.asyncDispose](): Promise<void>;
}

// Avvio methods
Expand Down
28 changes: 28 additions & 0 deletions test/symbol-async-dispose.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

const { test } = require('node:test')
const boot = require('..')

// asyncDispose doesn't exist in Node.js < 20
test('Symbol.asyncDispose should close avvio', { skip: !('asyncDispose' in Symbol) }, async (t) => {
t.plan(2)

const app = boot()
let closeHandlerCalled = false

app.use(function (server, opts, done) {
app.onClose(() => {
closeHandlerCalled = true
})
done()
})

await app.ready()

t.assert.strictEqual(app.booted, true)

// Simulates using keyword behavior: await using app = boot()
await app[Symbol.asyncDispose]()

t.assert.ok(closeHandlerCalled, 'onClose handler should be called')
})
Loading