Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support decorators #247

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@fastify/pre-commit": "^2.1.0",
"@fastify/type-provider-typebox": "^5.0.0-pre.fv5.1",
"@types/node": "^22.0.0",
"fastify": "^5.0.0-alpha.2",
"fastify": "git+https://github.com/livingspec/fastify.git#type-improvements",
"proxyquire": "^2.1.3",
"standard": "^17.1.0",
"tap": "^18.7.0",
Expand Down
40 changes: 27 additions & 13 deletions types/plugin.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/// <reference types="fastify" />

import {
FastifyPluginOptions,
AnyFastifyInstance,
UnEncapsulatedPlugin,
FastifyPluginCallback,
FastifyPluginAsync,
FastifyPluginOptions,
RawServerBase,
RawServerDefault,
FastifyTypeProvider,
FastifyTypeProviderDefault,
FastifyBaseLogger,
ApplyDependencies,
FastifyDependencies
} from 'fastify'

type FastifyPlugin = typeof fastifyPlugin
Expand Down Expand Up @@ -36,6 +35,18 @@ declare namespace fastifyPlugin {

export const fastifyPlugin: FastifyPlugin
export { fastifyPlugin as default }

export function createPlugin<
TPlugin extends FastifyPluginCallback,
TDependencies extends FastifyDependencies,
TEnhanced extends ApplyDependencies<TPlugin, TDependencies> = ApplyDependencies<TPlugin, TDependencies>
> (plugin: TEnhanced, options?: { dependencies?: TDependencies }): UnEncapsulatedPlugin<TEnhanced>

export function createPlugin<
TPlugin extends FastifyPluginAsync,
TDependencies extends FastifyDependencies,
TEnhanced extends ApplyDependencies<TPlugin, TDependencies> = ApplyDependencies<TPlugin, TDependencies>
> (plugin: TEnhanced, options?: { dependencies?: TDependencies }): UnEncapsulatedPlugin<TEnhanced>
}

/**
Expand All @@ -49,13 +60,16 @@ declare namespace fastifyPlugin {

declare function fastifyPlugin<
Options extends FastifyPluginOptions = Record<never, never>,
RawServer extends RawServerBase = RawServerDefault,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
Logger extends FastifyBaseLogger = FastifyBaseLogger,
Fn extends FastifyPluginCallback<Options, RawServer, TypeProvider, Logger> | FastifyPluginAsync<Options, RawServer, TypeProvider, Logger> = FastifyPluginCallback<Options, RawServer, TypeProvider, Logger>
>(
fn: Fn extends unknown ? Fn extends (...args: any) => Promise<any> ? FastifyPluginAsync<Options, RawServer, TypeProvider, Logger> : FastifyPluginCallback<Options, RawServer, TypeProvider, Logger> : Fn,
Plugin extends FastifyPluginCallback<Options, AnyFastifyInstance, AnyFastifyInstance> = FastifyPluginCallback<Options, AnyFastifyInstance, AnyFastifyInstance>>(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change: we're updating the number and type of generics. Can we avoid that? If not, I suspect we can only include it in the next major fastify version.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to keep the old version of fp. It was an open question whether or not we create a new util or update the old. A new util is possible, and "createPlugin" was added. I will make the change to keep fp as-is.

fn: Plugin,
options?: fastifyPlugin.PluginMetadata | string
): UnEncapsulatedPlugin<NoInfer<Plugin>>;

declare function fastifyPlugin<
Options extends FastifyPluginOptions = Record<never, never>,
Plugin extends FastifyPluginAsync<Options, AnyFastifyInstance, AnyFastifyInstance> = FastifyPluginAsync<Options, AnyFastifyInstance, AnyFastifyInstance>>(
fn: Plugin,
options?: fastifyPlugin.PluginMetadata | string
): Fn;
): UnEncapsulatedPlugin<NoInfer<Plugin>>;

export = fastifyPlugin
178 changes: 108 additions & 70 deletions types/plugin.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import fastifyPlugin from '..';
import fastify, { FastifyPluginCallback, FastifyPluginAsync, FastifyError, FastifyInstance, FastifyPluginOptions, RawServerDefault, FastifyTypeProviderDefault, FastifyBaseLogger } from 'fastify';
import fastify, {
FastifyPluginCallback,
FastifyPluginAsync,
FastifyError,
FastifyInstance,
FastifyPluginOptions,
UnEncapsulatedPlugin,
} from 'fastify';
import { expectAssignable, expectError, expectNotType, expectType } from 'tsd'
import { Server } from "node:https"
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox"
import fastifyExampleCallback from './example-callback.test-d';
import fastifyExampleAsync from './example-async.test-d';

interface Options {
foo: string
interface Options extends FastifyPluginOptions {
foo?: string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this change is needed 🤔

Suggested change
foo?: string
foo: string

}

const testSymbol = Symbol('foobar')

// Callback

const pluginCallback: FastifyPluginCallback = (fastify, options, next) => { }
expectType<FastifyPluginCallback>(fastifyPlugin(pluginCallback))
const pluginCallback = fastifyPlugin((fastify, options, next) => fastify)
expectAssignable<UnEncapsulatedPlugin<FastifyPluginCallback>>(pluginCallback)

const pluginCallbackWithTypes = (fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { }
expectAssignable<FastifyPluginCallback>(fastifyPlugin(pluginCallbackWithTypes))
expectNotType<any>(fastifyPlugin(pluginCallbackWithTypes))
const pluginCallbackWithTypes = fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next) => fastify)
expectType<UnEncapsulatedPlugin<FastifyPluginCallback<FastifyPluginOptions, FastifyInstance, FastifyInstance>>>(pluginCallbackWithTypes)
expectNotType<any>(pluginCallbackWithTypes)

expectAssignable<FastifyPluginCallback>(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { }))
expectNotType<any>(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { }))
expectAssignable<FastifyPluginCallback>(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void) => fastify))
expectNotType<any>(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void) => fastify))

expectType<FastifyPluginCallback>(fastifyPlugin(pluginCallback, ''))
expectType<FastifyPluginCallback>(fastifyPlugin(pluginCallback, {
expectAssignable<UnEncapsulatedPlugin<FastifyPluginCallback>>(fastifyPlugin(pluginCallback, ''))
expectAssignable<UnEncapsulatedPlugin<FastifyPluginCallback>>(fastifyPlugin(pluginCallback, {
fastify: '',
name: '',
decorators: {
Expand All @@ -37,33 +44,39 @@ expectType<FastifyPluginCallback>(fastifyPlugin(pluginCallback, {
encapsulate: true
}))

const pluginCallbackWithOptions: FastifyPluginCallback<Options> = (fastify, options, next) => {
expectType<string>(options.foo)
}

expectType<FastifyPluginCallback<Options>>(fastifyPlugin(pluginCallbackWithOptions))
const pluginCallbackWithOptions = fastifyPlugin((fastify, options: Options, next) => {
expectType<Options['foo']>(options.foo)
return fastify
})
expectAssignable<UnEncapsulatedPlugin<FastifyPluginCallback<Options>>>(pluginCallbackWithOptions)

const pluginCallbackWithServer: FastifyPluginCallback<Options, Server> = (fastify, options, next) => {
const pluginCallbackWithServer = fastifyPlugin((fastify: FastifyInstance<Server>, options, next) => {
expectType<Server>(fastify.server)
}

expectType<FastifyPluginCallback<Options, Server>>(fastifyPlugin(pluginCallbackWithServer))

const pluginCallbackWithTypeProvider: FastifyPluginCallback<Options, Server, TypeBoxTypeProvider> = (fastify, options, next) => { }

expectType<FastifyPluginCallback<Options, Server, TypeBoxTypeProvider>>(fastifyPlugin(pluginCallbackWithTypeProvider))
return fastify
})
expectAssignable<UnEncapsulatedPlugin<FastifyPluginCallback<FastifyPluginOptions, FastifyInstance<Server>, FastifyInstance<Server>>>>(pluginCallbackWithServer)

const pluginCallbackWithTypeProvider = fastifyPlugin((fastify: FastifyInstance, options: Options, next) => fastify.withTypeProvider<TypeBoxTypeProvider>())
expectAssignable<
UnEncapsulatedPlugin<
FastifyPluginCallback<
Options,
FastifyInstance,
FastifyInstance<Server, any, any, any, TypeBoxTypeProvider, any>
>
>
>(pluginCallbackWithTypeProvider)

// Async

const pluginAsync: FastifyPluginAsync = async (fastify, options) => { }
expectType<FastifyPluginAsync>(fastifyPlugin(pluginAsync))
const pluginAsync = fastifyPlugin(async (fastify, options) => fastify)
expectAssignable<UnEncapsulatedPlugin<FastifyPluginAsync>>(pluginAsync)

const pluginAsyncWithTypes = async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => { }
expectType<FastifyPluginAsync<FastifyPluginOptions, RawServerDefault, FastifyTypeProviderDefault>>(fastifyPlugin(pluginAsyncWithTypes))
const pluginAsyncWithTypes = fastifyPlugin(async (fastify: FastifyInstance, options: FastifyPluginOptions) => fastify)
expectType<UnEncapsulatedPlugin<FastifyPluginAsync<FastifyPluginOptions, FastifyInstance, FastifyInstance>>>(pluginAsyncWithTypes)

expectType<FastifyPluginAsync<FastifyPluginOptions, RawServerDefault, FastifyTypeProviderDefault>>(fastifyPlugin(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => { }))
expectType<FastifyPluginAsync>(fastifyPlugin(pluginAsync, ''))
expectType<FastifyPluginAsync>(fastifyPlugin(pluginAsync, {
expectAssignable<UnEncapsulatedPlugin<FastifyPluginAsync>>(fastifyPlugin(pluginAsync, ''))
expectAssignable<UnEncapsulatedPlugin<FastifyPluginAsync>>(fastifyPlugin(pluginAsync, {
fastify: '',
name: '',
decorators: {
Expand All @@ -75,92 +88,117 @@ expectType<FastifyPluginAsync>(fastifyPlugin(pluginAsync, {
encapsulate: true
}))

const pluginAsyncWithOptions: FastifyPluginAsync<Options> = async (fastify, options) => {
expectType<string>(options.foo)
}

expectType<FastifyPluginAsync<Options>>(fastifyPlugin(pluginAsyncWithOptions))
const pluginAsyncWithOptions = fastifyPlugin(async (fastify, options: Options) => {
expectType<Options['foo']>(options.foo)
return fastify
})
expectAssignable<UnEncapsulatedPlugin<FastifyPluginAsync<Options>>>(pluginAsyncWithOptions)

const pluginAsyncWithServer: FastifyPluginAsync<Options, Server> = async (fastify, options) => {
const pluginAsyncWithServer = fastifyPlugin(async (fastify: FastifyInstance<Server>, options) => {
expectType<Server>(fastify.server)
}

expectType<FastifyPluginAsync<Options, Server>>(fastifyPlugin(pluginAsyncWithServer))

const pluginAsyncWithTypeProvider: FastifyPluginAsync<Options, Server, TypeBoxTypeProvider> = async (fastify, options) => { }

expectType<FastifyPluginAsync<Options, Server, TypeBoxTypeProvider>>(fastifyPlugin(pluginAsyncWithTypeProvider))
return fastify
})
expectAssignable<UnEncapsulatedPlugin<FastifyPluginAsync<FastifyPluginOptions, FastifyInstance<Server>, FastifyInstance<Server>>>>(pluginAsyncWithServer)

const pluginAsyncWithTypeProvider = fastifyPlugin(async (fastify: FastifyInstance, options: Options) => fastify.withTypeProvider<TypeBoxTypeProvider>())
expectAssignable<
UnEncapsulatedPlugin<
FastifyPluginAsync<
Options,
FastifyInstance,
FastifyInstance<Server, any, any, any, TypeBoxTypeProvider, any>
>
>
>(pluginAsyncWithTypeProvider)

// Fastify register

const server = fastify()
server.register(fastifyPlugin(pluginCallback))
server.register(fastifyPlugin(pluginCallbackWithTypes))
server.register(fastifyPlugin(pluginCallbackWithOptions))
server.register(fastifyPlugin(pluginCallbackWithServer))
server.register(fastifyPlugin(pluginCallbackWithTypeProvider))
server.register(fastifyPlugin(pluginAsync))
server.register(fastifyPlugin(pluginAsyncWithTypes))
server.register(fastifyPlugin(pluginAsyncWithOptions))
server.register(fastifyPlugin(pluginAsyncWithServer))
server.register(fastifyPlugin(pluginAsyncWithTypeProvider))
server.register(pluginCallback)
server.register(pluginCallbackWithTypes)
server.register(pluginCallbackWithOptions)
// TODO
// server.register(pluginCallbackWithServer)
server.register(pluginCallbackWithTypeProvider)
server.register(pluginAsync)
server.register(pluginAsyncWithTypes)
server.register(pluginAsyncWithOptions)
// TODO
// server.register(pluginAsyncWithServer)
server.register(pluginAsyncWithTypeProvider)

// properly handling callback and async
fastifyPlugin(function (fastify, options, next) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Record<never, never>>(options)
expectType<(err?: Error) => void>(next)
return fastify
})

fastifyPlugin<Options>(function (fastify, options, next) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
expectType<(err?: Error) => void>(next)
return fastify
})

fastifyPlugin<Options>(async function (fastify, options) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
return fastify
})

expectAssignable<FastifyPluginAsync<Options, RawServerDefault, FastifyTypeProviderDefault, FastifyBaseLogger>>(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) { }))
expectNotType<any>(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) { }))
expectAssignable<FastifyPluginAsync<Options>>(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) {
return fastify
}))
expectNotType<any>(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) {
return fastify
}))

fastifyPlugin(async function (fastify, options: Options) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
return fastify
})

fastifyPlugin(async function (fastify, options) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Record<never, never>>(options)
return fastify
})

expectError(
fastifyPlugin(async function (fastify, options: Options, next) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
return fastify
})
)
expectAssignable<FastifyPluginCallback<Options>>(fastifyPlugin(function (fastify, options, next) { }))
expectNotType<any>(fastifyPlugin(function (fastify, options, next) { }))
expectAssignable<FastifyPluginCallback<Options>>(fastifyPlugin(function (fastify, options, next) {
return fastify
}))
expectNotType<any>(fastifyPlugin(function (fastify, options, next) {
return fastify
}))

fastifyPlugin(function (fastify, options: Options, next) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
expectType<(err?: Error) => void>(next)

return fastify
})

expectError(
fastifyPlugin(function (fastify, options: Options, next) {
expectType<FastifyInstance>(fastify)
expectAssignable<FastifyInstance>(fastify)
expectType<Options>(options)
return Promise.resolve()
return Promise.resolve(fastify)
})
)

server.register(fastifyExampleCallback, { foo: 'bar' })
server.register(fastifyExampleCallback, { foo: 'bar' } as const)
expectError(server.register(fastifyExampleCallback, { foo: 'baz' }))

server.register(fastifyExampleAsync, { foo: 'bar' })
server.register(fastifyExampleAsync, { foo: 'bar' } as const)
expectError(server.register(fastifyExampleAsync, { foo: 'baz' }))