diff --git a/.husky/pre-commit b/.husky/pre-commit index 1dc4645..d1096ab 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run test:ci +npm run test diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..1dc4645 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run test:ci diff --git a/README.md b/README.md index 0b044b5..dabe9fe 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ When the `close` event is triggered, the plugin will check if the [`AbortSignal` Is guaranteed that one and just one `AbortController` and `AbortSignal` will be made per request. -If the request was not aborted during its lifetime, the plugin will remove the `AbortController` and `AbortSignal` from the cache. This by scheduling a callback on the hook `onResponse`. +If the request was not aborted during its lifetime, the plugin will remove the `AbortController` and `AbortSignal` from the cache. This by scheduling a hook-handler on the hook `onResponse`. If the request aborted, the same hook will be used for cleaning resources. -A [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is used under the hood for caching, ensuring that the `AbortController` and `AbortSignal` instances can be unliked if not needed anymore, and for instance GC'ed. +A [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is used under the hood for caching, ensuring that the `AbortController` and `AbortSignal` instances can be unlinked if not needed anymore, and for instance GC'ed. ## Setup diff --git a/index.d.ts b/index.d.ts index 7e3a61c..af079e3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1 +1,34 @@ /// +import { FastifyPluginCallback } from 'fastify' +import { FastifyError } from '@fastify/error' + +export interface FastifyRacingSignal extends AbortSignal { + then: ( + onFulfilled?: (value: AbortEvent) => void | PromiseLike, + onRejected?: (reason: Error | FastifyError) => void | PromiseLike + ) => void | Promise +} + +export interface AbortEvent { + type: 'abort' | string + reason?: FastifyError | Error +} + +export interface FastifyRacingOptions { + handleError?: boolean + onRequestClosed?: ((evt: AbortEvent) => void) | null +} + +declare module 'fastify' { + interface FastifyRequest { + race(cb: FastifyRacingOptions['onRequestClosed']): void + race(opts: Omit): FastifyRacingSignal + race(opts: Omit): Promise + race(): FastifyRacingSignal + race(): Promise + } +} + +declare const FastifyRacing: FastifyPluginCallback + +export default FastifyRacing diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 37faede..140c7f4 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -1,26 +1,223 @@ -/// -import { FastifyPluginCallback } from 'fastify'; -import { FastifyError } from '@fastify/error'; - - -interface AbortEvent { - type: 'abort' | string; - reason?: FastifyError | Error -} - -interface FastifyRacing { - handleError?: boolean; - onRequestClosed?: (evt: AbortEvent) => void; -} - -declare module 'fastify' { - interface FastifyInstance { - race(cb: FastifyRacing['onRequestClosed']): void - race(opts: Omit): Promise - race(): Promise +import { expectType } from 'tsd' + +import fastify from 'fastify' +import plugin, { AbortEvent, FastifyRacingSignal } from '..' + +const serverHttp = fastify() + +serverHttp.register(plugin) + +serverHttp.register(plugin, { + handleError: true, + onRequestClosed: null +}) + +serverHttp.get( + '/', + { + preHandler: async (request, _reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + }, + async (request, reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } +) + +// -> Second level +serverHttp.register( + function (fastifyInstance, opts, done) { + fastifyInstance.register(plugin) + + fastifyInstance.get( + '/', + { + preHandler: async (request, _reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + }, + async (request, reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + ) + + done() + }, + { prefix: '/api' } +) + +const serverHttp2 = fastify({ http2: true }) + +serverHttp2.register(plugin, { + handleError: true, + onRequestClosed: null +}) + +serverHttp2.get( + '/', + { + preHandler: async (request, _reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + }, + async (request, reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) } -} +) + +// -> First plugin +serverHttp2.register( + function (fastifyInstance, opts, done) { + fastifyInstance.register(plugin) + + fastifyInstance.get( + '/', + { + preHandler: async (request, _reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) + + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + }, + async (request, reply) => { + const signal = request.race() + const signal2 = request.race({ + handleError: true + }) + const event = await request.race() + const event2 = await request.race({ + handleError: true + }) + + const asVoid = request.race(evt => { + expectType(evt) + }) -declare const FastifyRacing: FastifyPluginCallback; + expectType(asVoid) + expectType(signal) + expectType(event) + expectType(signal2) + expectType(event2) + } + ) -export default FastifyRacing; \ No newline at end of file + done() + }, + { prefix: '/api' } +)