Skip to content
Open
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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,41 @@ module.exports = fp(async function (fastify, opts) {
})
```

### `createPlugin()`

For TypeScript users that want Fastify to infer decorators from plugin return
values, `fastify-plugin` also exposes `createPlugin()`.

```ts
import { createPlugin } from 'fastify-plugin'

export default createPlugin(async (fastify) => {
return fastify.decorate('usersRepository', {
findAll () {
return []
}
})
})
```

`createPlugin()` also accepts type-level plugin dependencies so a plugin can
express that it expects decorators from previously registered plugins:

```ts
import { createPlugin } from 'fastify-plugin'
import dbPlugin from './db-plugin'

export default createPlugin((fastify) => {
fastify.usersRepository.findAll()
return fastify
}, {
dependencies: [dbPlugin]
})
```

Use `fp()` for legacy plugins and runtime metadata checks. Use `createPlugin()`
when you want inference-friendly plugin composition.

## Metadata
In addition, if you use this module when creating new plugins, you can declare the dependencies, the name, and the expected Fastify version that your plugin needs.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@types/node": "^25.0.3",
"c8": "^11.0.0",
"eslint": "^9.17.0",
"fastify": "^5.0.0",
"fastify": "github:fastify/fastify#feat/typed-decorators",
"neostandard": "^0.12.0",
"proxyquire": "^2.1.3",
"tsd": "^0.33.0"
Expand Down
11 changes: 11 additions & 0 deletions plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ function plugin (fn, options = {}) {
return fn
}

function createPlugin (fn, options = {}) {
const runtimeOptions = { ...options }

if (Array.isArray(runtimeOptions.dependencies)) {
delete runtimeOptions.dependencies
}

return plugin(fn, runtimeOptions)
}

module.exports = plugin
module.exports.default = plugin
module.exports.fastifyPlugin = plugin
module.exports.createPlugin = createPlugin
44 changes: 44 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,50 @@ test('fastify-plugin is a function', (t) => {
t.assert.ok(typeof fp === 'function')
})

test('createPlugin is exported', (t) => {
t.plan(1)
t.assert.ok(typeof fp.createPlugin === 'function')
})

test('createPlugin removes runtime dependencies array metadata', (t) => {
t.plan(1)

function plugin (_fastify, _opts, next) {
next()
}

const wrapped = fp.createPlugin(plugin, {
dependencies: ['a', 'b'],
fastify: '5.x',
name: 'runtime-deps-test'
})

t.assert.deepStrictEqual(wrapped[Symbol.for('plugin-meta')], {
fastify: '5.x',
name: 'runtime-deps-test'
})
})

test('createPlugin keeps non-array dependencies metadata untouched', (t) => {
t.plan(1)

function plugin (_fastify, _opts, next) {
next()
}

const wrapped = fp.createPlugin(plugin, {
dependencies: 'legacy-dependency',
fastify: '5.x',
name: 'runtime-deps-string-test'
})

t.assert.deepStrictEqual(wrapped[Symbol.for('plugin-meta')], {
dependencies: 'legacy-dependency',
fastify: '5.x',
name: 'runtime-deps-string-test'
})
})

test('should return the function with the skip-override Symbol', (t) => {
t.plan(1)

Expand Down
65 changes: 49 additions & 16 deletions types/plugin.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import {
FastifyPluginCallback,
ApplyDependencies,
FastifyDependencies,
FastifyPluginAsync,
FastifyPluginOptions,
RawServerBase,
RawServerDefault,
FastifyTypeProvider,
FastifyTypeProviderDefault,
FastifyBaseLogger,
FastifyPluginCallback,
UnEncapsulatedPlugin
} from 'fastify'

type FastifyPlugin = typeof fastifyPlugin
Expand All @@ -26,6 +23,15 @@ declare namespace fastifyPlugin {
dependencies?: string[],
encapsulate?: boolean
}

/**
* Metadata accepted by `createPlugin()`.
* `dependencies` are type-level plugin dependencies rather than runtime plugin names.
*/
export interface CreatePluginMetadata<TDependencies extends FastifyDependencies> extends Omit<PluginMetadata, 'dependencies'> {
dependencies?: TDependencies
}

// Exporting PluginOptions for backward compatibility after renaming it to PluginMetadata
/**
* @deprecated Use PluginMetadata instead
Expand All @@ -34,6 +40,34 @@ declare namespace fastifyPlugin {

export const fastifyPlugin: FastifyPlugin
export { fastifyPlugin as default }

export function createPlugin <TPlugin extends (...args: any[]) => any> (
plugin: TPlugin extends FastifyPluginCallback<any> ? TPlugin : never,
options?: Omit<PluginMetadata, 'dependencies'>
): UnEncapsulatedPlugin<TPlugin>

export function createPlugin <
TPlugin extends (...args: any[]) => any,
TDependencies extends FastifyDependencies,
TEnhanced extends ApplyDependencies<Extract<TPlugin, FastifyPluginCallback<any>>, TDependencies> = ApplyDependencies<Extract<TPlugin, FastifyPluginCallback<any>>, TDependencies>
> (
plugin: TEnhanced,
options: CreatePluginMetadata<TDependencies>
): UnEncapsulatedPlugin<TEnhanced>

export function createPlugin <TPlugin extends (...args: any[]) => any> (
plugin: TPlugin extends FastifyPluginAsync<any> ? TPlugin : never,
options?: Omit<PluginMetadata, 'dependencies'>
): UnEncapsulatedPlugin<TPlugin>

export function createPlugin <
TPlugin extends (...args: any[]) => any,
TDependencies extends FastifyDependencies,
TEnhanced extends ApplyDependencies<Extract<TPlugin, FastifyPluginAsync<any>>, TDependencies> = ApplyDependencies<Extract<TPlugin, FastifyPluginAsync<any>>, TDependencies>
> (
plugin: TEnhanced,
options: CreatePluginMetadata<TDependencies>
): UnEncapsulatedPlugin<TEnhanced>
}

/**
Expand All @@ -45,15 +79,14 @@ declare namespace fastifyPlugin {
* @param options Optional plugin options
*/

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,
declare function fastifyPlugin <TPlugin extends (...args: any[]) => any> (
fn: TPlugin extends FastifyPluginCallback<any> ? TPlugin : never,
options?: fastifyPlugin.PluginMetadata | string
): UnEncapsulatedPlugin<NoInfer<TPlugin>>

declare function fastifyPlugin <TPlugin extends (...args: any[]) => any> (
fn: TPlugin extends FastifyPluginAsync<any> ? TPlugin : never,
options?: fastifyPlugin.PluginMetadata | string
): Fn
): UnEncapsulatedPlugin<NoInfer<TPlugin>>

export = fastifyPlugin
Loading
Loading