Skip to content

Commit fb4b3bd

Browse files
authoredOct 9, 2023
feat: Add Symbol.asyncDispose to improve DX in short lived servers. (#5082)
* feat: dispose * fix: check if asyncDispose exists * feat: ts typings * tests: symbol.dispose types * fix: dispose return type and tests * docs: added basic docs * tests: .test * docs * pass if asyncDispose is not present * tests: better skip * tests: plan of 2
1 parent 912f49d commit fb4b3bd

File tree

5 files changed

+76
-3
lines changed

5 files changed

+76
-3
lines changed
 

‎docs/Reference/Server.md

+31-3
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,21 @@ describes the properties available in that options object.
5050
- [after](#after)
5151
- [ready](#ready)
5252
- [listen](#listen)
53+
- [`listenTextResolver`](#listentextresolver)
5354
- [addresses](#addresses)
5455
- [getDefaultRoute](#getdefaultroute)
5556
- [setDefaultRoute](#setdefaultroute)
5657
- [routing](#routing)
5758
- [route](#route)
58-
- [hasRoute](#hasRoute)
59+
- [hasRoute](#hasroute)
5960
- [close](#close)
60-
- [decorate*](#decorate)
61+
- [decorate\*](#decorate)
6162
- [register](#register)
6263
- [addHook](#addhook)
6364
- [prefix](#prefix)
6465
- [pluginName](#pluginname)
6566
- [hasPlugin](#hasplugin)
66-
- [listeningOrigin](#listeningOrigin)
67+
- [listeningOrigin](#listeningorigin)
6768
- [log](#log)
6869
- [version](#version)
6970
- [inject](#inject)
@@ -93,6 +94,7 @@ describes the properties available in that options object.
9394
- [defaultTextParser](#defaulttextparser)
9495
- [errorHandler](#errorhandler)
9596
- [childLoggerFactory](#childloggerfactory)
97+
- [Symbol.asyncDispose](#symbolasyncdispose)
9698
- [initialConfig](#initialconfig)
9799

98100
### `http`
@@ -1866,6 +1868,32 @@ fastify.get('/', {
18661868
Fastify instance. See the [`childLoggerFactory` config option](#setchildloggerfactory)
18671869
for more info.
18681870
1871+
#### Symbol.asyncDispose
1872+
<a id="symbolAsyncDispose"></a>
1873+
1874+
`fastify[Symbol.asyncDispose]` is a symbol that can be used to define an
1875+
asynchronous function that will be called when the Fastify instance is closed.
1876+
1877+
Its commonly used alongside the `using` typescript keyword to ensure that
1878+
resources are cleaned up when the Fastify instance is closed.
1879+
1880+
This combines perfectly inside short lived processes or unit tests, where you must
1881+
close all fastify resources after returning from inside the function.
1882+
1883+
```ts
1884+
it('Uses app and closes it afterwards', async () => {
1885+
await using app = fastify();
1886+
// do something with app.
1887+
})
1888+
```
1889+
1890+
In the above example, fastify is closed automatically after the test finishes.
1891+
1892+
Read more about the
1893+
[ECMAScript Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management)
1894+
and the [using keyword](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/)
1895+
introduced in typescript 5.2.
1896+
18691897
#### initialConfig
18701898
<a id="initial-config"></a>
18711899

‎fastify.js

+7
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,13 @@ function fastify (options) {
506506
// versions of Node.js. In that event, we don't care, so ignore the error.
507507
}
508508

509+
// Older nodejs versions may not have asyncDispose
510+
if ('asyncDispose' in Symbol) {
511+
fastify[Symbol.asyncDispose] = function dispose () {
512+
return fastify.close()
513+
}
514+
}
515+
509516
return fastify
510517

511518
function throwIfAlreadyStarted (msg) {

‎test/async-dispose.test.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict'
2+
3+
const t = require('tap')
4+
const Fastify = require('../fastify')
5+
6+
// asyncDispose doesn't exist in node <= 16
7+
t.test('async dispose should close fastify', { skip: !('asyncDispose' in Symbol) }, async t => {
8+
t.plan(2)
9+
10+
const fastify = Fastify()
11+
12+
await fastify.listen({ port: 0 })
13+
14+
t.equal(fastify.server.listening, true)
15+
16+
// the same as syntax sugar for
17+
// await using app = fastify()
18+
await fastify[Symbol.asyncDispose]()
19+
20+
t.equal(fastify.server.listening, false)
21+
})

‎test/types/using.test-d.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expectAssignable, expectType } from 'tsd'
2+
import fastify, { FastifyInstance } from '../../fastify'
3+
4+
async function hasSymbolDisposeWithUsing () {
5+
await using app = fastify()
6+
expectAssignable<FastifyInstance>(app)
7+
expectAssignable<FastifyInstance[typeof Symbol.asyncDispose]>(app.close)
8+
}
9+
10+
async function hasSymbolDispose () {
11+
const app = fastify()
12+
expectAssignable<FastifyInstance>(app)
13+
expectAssignable<FastifyInstance[typeof Symbol.asyncDispose]>(app.close)
14+
}

‎types/instance.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ export interface FastifyInstance<
142142
close(): Promise<undefined>;
143143
close(closeListener: () => void): undefined;
144144

145+
/** Alias for {@linkcode FastifyInstance.close()} */
146+
[Symbol.asyncDispose](): Promise<undefined>;
147+
145148
// should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to
146149
decorate: DecorationMethod<FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
147150
decorateRequest: DecorationMethod<FastifyRequest, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;

0 commit comments

Comments
 (0)
Please sign in to comment.