@fastify/autoload
Advanced tools
| name: Lock Threads | ||
| on: | ||
| schedule: | ||
| - cron: '0 0 1 * *' | ||
| workflow_dispatch: | ||
| concurrency: | ||
| group: lock | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
| lock-threads: | ||
| permissions: | ||
| issues: write | ||
| pull-requests: write | ||
| uses: fastify/workflows/.github/workflows/lock-threads.yml@v6 |
| 'use strict' | ||
| const { exec } = require('node:child_process') | ||
| const { argv } = require('node:process') | ||
| const args = [ | ||
| argv[0], | ||
| 'test', | ||
| './test/typescript-esm/forceESM.ts' | ||
| ] | ||
| const child = exec(args.join(' '), { | ||
| shell: true, | ||
| env: { | ||
| ...process.env, | ||
| TS_NODE_COMPILER_OPTIONS: JSON.stringify({ | ||
| module: 'ESNext', | ||
| target: 'ES2020', | ||
| allowJs: false, | ||
| moduleResolution: 'node', | ||
| esModuleInterop: true | ||
| }) | ||
| } | ||
| }) | ||
| child.stdout.pipe(process.stdout) | ||
| child.stderr.pipe(process.stderr) | ||
| child.once('close', process.exit) |
| 'use strict' | ||
| const { exec } = require('node:child_process') | ||
| const { argv } = require('node:process') | ||
| const args = [ | ||
| argv[0], | ||
| 'test', | ||
| '-A', | ||
| './test/typescript-esm/forceESM.ts' | ||
| ] | ||
| const child = exec(args.join(' '), { | ||
| shell: true, | ||
| env: { | ||
| ...process.env, | ||
| TS_NODE_COMPILER_OPTIONS: JSON.stringify({ | ||
| module: 'ESNext', | ||
| target: 'ES2020', | ||
| allowJs: false, | ||
| moduleResolution: 'node', | ||
| esModuleInterop: true | ||
| }) | ||
| } | ||
| }) | ||
| child.stdout.pipe(process.stdout) | ||
| child.stderr.pipe(process.stderr) | ||
| child.once('close', process.exit) |
| 'use strict' | ||
| module.exports = async (fastify) => { | ||
| fastify.get('/', function () { | ||
| return { foo: 'bar' } | ||
| }) | ||
| } |
| 'use strict' | ||
| module.exports = async (fastify) => { | ||
| fastify.get('/', function () { | ||
| return { hello: 'world' } | ||
| }) | ||
| } |
| 'use strict' | ||
| module.exports = async function (app) { | ||
| app.get('/alive', async function () { | ||
| return { ok: true } | ||
| }) | ||
| } |
| 'use strict' | ||
| module.exports = async function (app) { | ||
| app.setNotFoundHandler(function (req, reply) { | ||
| reply.status(404).send({ scope: 'api', url: req.url }) | ||
| }) | ||
| } |
| 'use strict' | ||
| const { after, before, describe, it } = require('node:test') | ||
| const path = require('node:path') | ||
| const Fastify = require('fastify') | ||
| const autoLoad = require('../../../') | ||
| const assert = require('node:assert') | ||
| describe('Issue 326: setNotFoundHandler from autohooks must remain under prefixed scope', function () { | ||
| const app = Fastify() | ||
| before(async function () { | ||
| app.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes', 'api'), | ||
| autoHooks: true, | ||
| cascadeHooks: true, | ||
| options: { prefix: '/api' } | ||
| }) | ||
| await app.ready() | ||
| }) | ||
| after(async function () { | ||
| await app.close() | ||
| }) | ||
| it('keeps autohooks notFound handler for prefixed paths only', async function () { | ||
| const prefixed = await app.inject({ method: 'GET', url: '/api/not-exists' }) | ||
| assert.strictEqual(prefixed.statusCode, 404) | ||
| assert.deepStrictEqual(prefixed.json(), { scope: 'api', url: '/api/not-exists' }) | ||
| const root = await app.inject({ method: 'GET', url: '/not-exists' }) | ||
| assert.strictEqual(root.statusCode, 404) | ||
| assert.notDeepStrictEqual(root.json(), { scope: 'api', url: '/not-exists' }) | ||
| assert.strictEqual(root.json().scope, undefined) | ||
| }) | ||
| }) |
| 'use strict' | ||
| module.exports = async function (fastify) { | ||
| fastify.get('/', async () => ({ zone: 'private' })) | ||
| } |
| 'use strict' | ||
| module.exports = async function (fastify) { | ||
| fastify.get('/', async () => ({ zone: 'public' })) | ||
| } |
| 'use strict' | ||
| const { afterEach, describe, it } = require('node:test') | ||
| const assert = require('node:assert') | ||
| const path = require('node:path') | ||
| const Fastify = require('fastify') | ||
| const autoLoad = require('../../../') | ||
| describe('Issue 519: ignorePattern should match against the relative file path, not just the entry basename', function () { | ||
| let app | ||
| afterEach(async function () { | ||
| await app.close() | ||
| }) | ||
| it('ignorePattern with a path-component regex skips only the matched subdirectory file', async function () { | ||
| app = Fastify() | ||
| app.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| ignorePattern: /private\/plugin/, | ||
| }) | ||
| await app.ready() | ||
| const pub = await app.inject({ url: '/public' }) | ||
| assert.strictEqual(pub.statusCode, 200) | ||
| assert.deepStrictEqual(pub.json(), { zone: 'public' }) | ||
| const priv = await app.inject({ url: '/private' }) | ||
| assert.strictEqual(priv.statusCode, 404, 'private route should be excluded by ignorePattern path match') | ||
| }) | ||
| }) |
| import fastify, { FastifyInstance, FastifyPluginCallback } from 'fastify' | ||
| import { expect } from 'tstyche' | ||
| import * as fastifyAutoloadStar from '.' | ||
| import fastifyAutoloadDefault, { AutoloadPluginOptions, fastifyAutoload as fastifyAutoloadNamed } from '.' | ||
| import * as fastifyAutoloadCjsImport from '.' | ||
| const fastifyAutoloadCjs = require('..') | ||
| const app: FastifyInstance = fastify() | ||
| app.register(fastifyAutoloadNamed, { dir: 'test' }) | ||
| app.register(fastifyAutoloadDefault, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjs, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjsImport.default, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjsImport.fastifyAutoload, { dir: 'test' }) | ||
| app.register(fastifyAutoloadStar.default, { dir: 'test' }) | ||
| app.register(fastifyAutoloadStar.fastifyAutoload, { dir: 'test' }) | ||
| expect(fastifyAutoloadNamed).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadDefault).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadCjsImport.default).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadCjsImport.fastifyAutoload).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadStar.default).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadStar.fastifyAutoload).type.toBe<FastifyPluginCallback<AutoloadPluginOptions>>() | ||
| expect(fastifyAutoloadCjs).type.toBe<any>() | ||
| const opt1: AutoloadPluginOptions = { | ||
| dir: 'test' | ||
| } | ||
| const opt2: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignorePattern: /skip/ | ||
| } | ||
| const opt3: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| scriptPattern: /js/, | ||
| indexPattern: /index/, | ||
| } | ||
| const opt4: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| options: { | ||
| prefix: 'test' | ||
| } | ||
| } | ||
| const opt5: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| maxDepth: 1, | ||
| } | ||
| const opt6: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| routeParams: true, | ||
| } | ||
| const opt7: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| forceESM: true, | ||
| autoHooks: true, | ||
| autoHooksPattern: /^[_.]?auto_?hooks(?:\.ts|\.js|\.cjs|\.mjs)$/i, | ||
| cascadeHooks: true, | ||
| overwriteHooks: true, | ||
| } | ||
| const opt8: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| encapsulate: false, | ||
| } | ||
| const opt9: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: /test/, | ||
| matchFilter: /handler/ | ||
| } | ||
| const opt10: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: 'test', | ||
| matchFilter: 'handler' | ||
| } | ||
| const opt11: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: (path) => path.endsWith('.spec.ts'), | ||
| matchFilter: (path) => path.split('/').at(-2) === 'handlers' | ||
| } | ||
| app.register(fastifyAutoloadDefault, opt1) | ||
| app.register(fastifyAutoloadDefault, opt2) | ||
| app.register(fastifyAutoloadDefault, opt3) | ||
| app.register(fastifyAutoloadDefault, opt4) | ||
| app.register(fastifyAutoloadDefault, opt5) | ||
| app.register(fastifyAutoloadDefault, opt6) | ||
| app.register(fastifyAutoloadDefault, opt7) | ||
| app.register(fastifyAutoloadDefault, opt8) | ||
| app.register(fastifyAutoloadDefault, opt9) | ||
| app.register(fastifyAutoloadDefault, opt10) | ||
| app.register(fastifyAutoloadDefault, opt11) | ||
| expect(app.register).type.not.toBeCallableWith(fastifyAutoloadDefault, { | ||
| dir: 'test', | ||
| invalidOption: true | ||
| }) |
@@ -5,4 +5,13 @@ version: 2 | ||
| directory: "/" | ||
| commit-message: | ||
| # Prefix all commit messages with "chore: " | ||
| prefix: "chore" | ||
| schedule: | ||
| interval: "monthly" | ||
| interval: "weekly" | ||
| cooldown: | ||
| default-days: 7 | ||
| allow: | ||
| - dependency-name: "*" | ||
| update-types: | ||
| - "version-update:semver-major" | ||
| open-pull-requests-limit: 10 | ||
@@ -12,4 +21,35 @@ | ||
| directory: "/" | ||
| commit-message: | ||
| # Prefix all commit messages with "chore: " | ||
| prefix: "chore" | ||
| schedule: | ||
| interval: "monthly" | ||
| interval: "weekly" | ||
| cooldown: | ||
| default-days: 7 | ||
| versioning-strategy: "increase-if-necessary" | ||
| allow: | ||
| - dependency-name: "*" | ||
| update-types: | ||
| - "version-update:semver-major" | ||
| ignore: | ||
| # TODO: remove ignore until neostandard support ESLint 10 | ||
| - dependency-name: "eslint" | ||
| - dependency-name: "neostandard" | ||
| - dependency-name: "@stylistic/*" | ||
| open-pull-requests-limit: 10 | ||
| groups: | ||
| # Production dependencies with breaking changes | ||
| dependencies: | ||
| dependency-type: "production" | ||
| # ESLint related dependencies | ||
| dev-dependencies-eslint: | ||
| patterns: | ||
| - "eslint" | ||
| - "neostandard" | ||
| - "@stylistic/*" | ||
| # TypeScript related dependencies | ||
| dev-dependencies-typescript: | ||
| patterns: | ||
| - "@types/*" | ||
| - "tstyche" | ||
| - "typescript" |
@@ -17,2 +17,7 @@ name: CI | ||
| # This allows a subsequently queued workflow run to interrupt previous runs | ||
| concurrency: | ||
| group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" | ||
| cancel-in-progress: true | ||
| permissions: | ||
@@ -22,2 +27,36 @@ contents: read | ||
| jobs: | ||
| bun: | ||
| runs-on: ubuntu-latest | ||
| continue-on-error: true | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 | ||
| - name: Install dependencies | ||
| run: bun install | ||
| - name: Run unit tests | ||
| run: bun run typescript:bun | ||
| deno: | ||
| runs-on: ubuntu-latest | ||
| continue-on-error: true | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Setup Deno | ||
| uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb | ||
| - name: Install dependencies | ||
| run: deno install | ||
| - name: Run unit tests | ||
| run: deno run typescript:deno | ||
| test: | ||
@@ -27,5 +66,5 @@ permissions: | ||
| pull-requests: write | ||
| uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5 | ||
| uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6 | ||
| with: | ||
| license-check: true | ||
| lint: true |
+64
-2
@@ -138,2 +138,6 @@ 'use strict' | ||
| } else { | ||
| const hooksPrefix = findCommonHooksPrefix(node) | ||
| const scopedPluginsMeta = hooksPrefix ? buildScopedPluginsMeta(node.pluginsMeta, hooksPrefix) : node.pluginsMeta | ||
| const scopedNode = hooksPrefix ? { ...node, pluginsMeta: scopedPluginsMeta } : node | ||
| const composedPlugin = async function (app) { | ||
@@ -147,8 +151,66 @@ // find hook functions for this prefix | ||
| registerAllPlugins(app, node) | ||
| registerAllPlugins(app, scopedNode) | ||
| } | ||
| fastify.register(composedPlugin) | ||
| if (hooksPrefix) { | ||
| fastify.register(composedPlugin, { prefix: hooksPrefix }) | ||
| } else { | ||
| fastify.register(composedPlugin) | ||
| } | ||
| } | ||
| } | ||
| function findCommonHooksPrefix (node) { | ||
| const prefixes = Object.values(node.pluginsMeta) | ||
| .map((meta) => meta?.options?.prefix) | ||
| .filter((prefix) => typeof prefix === 'string' && prefix.length > 0) | ||
| if (prefixes.length === 0) { | ||
| return | ||
| } | ||
| const [first] = prefixes | ||
| if (prefixes.every((prefix) => prefix === first)) { | ||
| return first | ||
| } | ||
| } | ||
| function buildScopedPluginsMeta (pluginsMeta, hooksPrefix) { | ||
| const scopedPluginsMeta = {} | ||
| for (const [name, meta] of Object.entries(pluginsMeta)) { | ||
| const options = meta?.options | ||
| if (typeof options?.prefix === 'string') { | ||
| const strippedPrefix = stripPrefix(options.prefix, hooksPrefix) | ||
| scopedPluginsMeta[name] = { | ||
| ...meta, | ||
| options: { | ||
| ...options, | ||
| /* c8 ignore next */ | ||
| ...(strippedPrefix ? { prefix: strippedPrefix } : { prefix: undefined }) | ||
| }, | ||
| registered: false | ||
| } | ||
| } else { | ||
| scopedPluginsMeta[name] = { | ||
| ...meta, | ||
| registered: false | ||
| } | ||
| } | ||
| } | ||
| return scopedPluginsMeta | ||
| } | ||
| function stripPrefix (prefix, parentPrefix) { | ||
| /* c8 ignore next 3 */ | ||
| if (!prefix.startsWith(parentPrefix)) { | ||
| return prefix | ||
| } | ||
| const stripped = prefix.slice(parentPrefix.length) | ||
| /* c8 ignore next */ | ||
| return stripped === '' ? undefined : stripped | ||
| } | ||
| function registerAllPlugins (app, node) { | ||
@@ -155,0 +217,0 @@ const metas = Object.values(node.pluginsMeta) |
@@ -90,4 +90,7 @@ 'use strict' | ||
| for (const dirEntry of dirEntries) { | ||
| if (opts.ignorePattern && RegExp(opts.ignorePattern).test(dirEntry.name)) { | ||
| continue | ||
| if (opts.ignorePattern) { | ||
| const entryRelPath = relative(opts.dir, join(dir, dirEntry.name)).replace(/\\/gu, '/') | ||
| if (RegExp(opts.ignorePattern).test(entryRelPath)) { | ||
| continue | ||
| } | ||
| } | ||
@@ -94,0 +97,0 @@ |
+14
-7
@@ -39,2 +39,14 @@ 'use strict' | ||
| Object.defineProperties(runtime, { | ||
| bun: { | ||
| get () { | ||
| cache.bun ??= 'Bun' in globalThis | ||
| return cache.bun | ||
| } | ||
| }, | ||
| deno: { | ||
| get () { | ||
| cache.deno ??= 'Deno' in globalThis | ||
| return cache.deno | ||
| } | ||
| }, | ||
| tsNode: { | ||
@@ -102,8 +114,2 @@ get () { | ||
| }, | ||
| tsimp: { | ||
| get () { | ||
| cache.tsimp ??= checkProcessArgv('tsimp/import') | ||
| return cache.tsimp | ||
| } | ||
| }, | ||
| supportTypeScript: { | ||
@@ -113,2 +119,4 @@ get () { | ||
| checkEnvVariable('FASTIFY_AUTOLOAD_TYPESCRIPT') || | ||
| runtime.bun || | ||
| runtime.deno || | ||
| runtime.tsNode || | ||
@@ -122,3 +130,2 @@ runtime.vitest || | ||
| runtime.esbuild || | ||
| runtime.tsimp || | ||
| runtime.supportNativeTypeScript | ||
@@ -125,0 +132,0 @@ ) |
+1
-1
| MIT License | ||
| Copyright (c) 2018 Fastify | ||
| Copyright (c) 2018-present The Fastify team <https://github.com/fastify/fastify#team> | ||
@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
+33
-27
| { | ||
| "name": "@fastify/autoload", | ||
| "version": "6.3.1", | ||
| "version": "6.4.0", | ||
| "description": "Require all plugins in a directory", | ||
@@ -12,3 +12,5 @@ "main": "index.js", | ||
| "test": "npm run typescript && npm run typescript:native && npm run typescript:jest && npm run typescript:swc-node-register && npm run typescript:tsm && npm run typescript:tsx && npm run typescript:vitest && npm run typescript:esbuild && npm run unit", | ||
| "typescript": "tsd", | ||
| "typescript": "tstyche", | ||
| "typescript:bun": "bun scripts/unit-typescript-bun-esm.js", | ||
| "typescript:deno": "deno -A scripts/unit-typescript-deno-esm.js", | ||
| "typescript:jest": "jest", | ||
@@ -19,3 +21,2 @@ "typescript:esm": "node scripts/unit-typescript-esm.js", | ||
| "typescript:tsx": "node scripts/unit-typescript-tsx.js", | ||
| "typescript:tsimp": "node scripts/unit-typescript-tsimp.js", | ||
| "typescript:esbuild": "node scripts/unit-typescript-esbuild.js", | ||
@@ -46,3 +47,3 @@ "typescript:native": "node scripts/unit-typescript-native-type-stripping.js", | ||
| "name": "Tomas Della Vedova", | ||
| "url": "http://delved.org" | ||
| "url": "https://delvedor.dev" | ||
| }, | ||
@@ -79,27 +80,25 @@ { | ||
| "devDependencies": { | ||
| "@fastify/pre-commit": "^2.1.0", | ||
| "@fastify/url-data": "^6.0.0", | ||
| "@jsumners/line-reporter": "^1.0.1", | ||
| "@swc-node/register": "^1.9.1", | ||
| "@swc/core": "^1.5.25", | ||
| "@types/jest": "^29.5.12", | ||
| "@types/node": "^22.0.0", | ||
| "borp": "^0.20.0", | ||
| "esbuild": "^0.25.0", | ||
| "@swc-node/register": "1.11.1", | ||
| "@swc/core": "1.15.40", | ||
| "@types/jest": "^30.0.0", | ||
| "@types/node": "^26.0.0", | ||
| "borp": "^1.0.0", | ||
| "esbuild": "^0.28.0", | ||
| "esbuild-register": "^3.5.0", | ||
| "eslint": "^9.17.0", | ||
| "fastify": "^5.0.0", | ||
| "fastify-plugin": "^5.0.0", | ||
| "jest": "^29.7.0", | ||
| "neostandard": "^0.12.0", | ||
| "ts-jest": "^29.1.4", | ||
| "fastify-plugin": "^6.0.0", | ||
| "jest": "^30.0.3", | ||
| "neostandard": "^0.13.0", | ||
| "ts-jest": "^29.4.9", | ||
| "ts-node": "^10.9.2", | ||
| "ts-node-dev": "^2.0.0", | ||
| "tsd": "^0.32.0", | ||
| "tsimp": "^2.0.11", | ||
| "tstyche": "^7.0.0", | ||
| "tsm": "^2.3.0", | ||
| "tsx": "^4.15.7", | ||
| "typescript": "5.5", | ||
| "vite": "^6.0.2", | ||
| "vitest": "^3.0.4" | ||
| "tsx": "^4.21.0", | ||
| "typescript": "~6.0.2", | ||
| "vite": "^8.0.3", | ||
| "vitest": "^4.0.6" | ||
| }, | ||
@@ -114,3 +113,14 @@ "jest": { | ||
| "transform": { | ||
| "^.+\\.(ts|tsx)$": "ts-jest" | ||
| "^.+\\.(ts|tsx)$": [ | ||
| "ts-jest", | ||
| { | ||
| "tsconfig": { | ||
| "esModuleInterop": true, | ||
| "types": [ | ||
| "jest", | ||
| "node" | ||
| ] | ||
| } | ||
| } | ||
| ] | ||
| } | ||
@@ -120,7 +130,3 @@ }, | ||
| "access": "public" | ||
| }, | ||
| "pre-commit": [ | ||
| "lint", | ||
| "test" | ||
| ] | ||
| } | ||
| } |
+396
-357
@@ -93,231 +93,259 @@ # @fastify/autoload | ||
| - `dir` (required) - Base directory containing plugins to be loaded | ||
| ### `dir` (required) | ||
| Each script file within a directory is treated as a plugin unless the directory contains an index file (e.g. `index.js`). In which case, only the index file (and the potential sub-directories) will be loaded. | ||
| Base directory containing plugins to be loaded. | ||
| The following script types are supported: | ||
| Each script file within a directory is treated as a plugin unless the directory contains an index file (e.g. `index.js`). In which case, only the index file (and the potential sub-directories) will be loaded. | ||
| - `.js ` (CommonJS or ES modules depending on `type` field of parent `package.json`) | ||
| - `.cjs` (CommonJS) | ||
| - `.mjs` (ES modules) | ||
| - `.ts` (TypeScript) | ||
| The following script types are supported: | ||
| - `dirNameRoutePrefix` (optional) - Default: true. Determines whether routes will be automatically prefixed with the subdirectory name in an autoloaded directory. It can be a sync function that must return a string that will be used as prefix, or it must return `false` to skip the prefix for the directory. | ||
| - `.js ` (CommonJS or ES modules depending on `type` field of parent `package.json`) | ||
| - `.cjs` (CommonJS) | ||
| - `.mjs` (ES modules) | ||
| - `.ts` (TypeScript) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| dirNameRoutePrefix: false // lack of prefix will mean no prefix, instead of directory name | ||
| }) | ||
| ### `dirNameRoutePrefix` (optional) - Default: `true` | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| dirNameRoutePrefix: function rewrite (folderParent, folderName) { | ||
| if (folderName === 'YELLOW') { | ||
| return 'yellow-submarine' | ||
| } | ||
| if (folderName === 'FoOoO-BaAaR') { | ||
| return false | ||
| } | ||
| return folderName | ||
| Determines whether routes will be automatically prefixed with the subdirectory name in an autoloaded directory. It can be a sync function that must return a string that will be used as prefix, or it must return `false` to skip the prefix for the directory. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| dirNameRoutePrefix: false // lack of prefix will mean no prefix, instead of directory name | ||
| }) | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| dirNameRoutePrefix: function rewrite (folderParent, folderName) { | ||
| if (folderName === 'YELLOW') { | ||
| return 'yellow-submarine' | ||
| } | ||
| }) | ||
| ``` | ||
| if (folderName === 'FoOoO-BaAaR') { | ||
| return false | ||
| } | ||
| return folderName | ||
| } | ||
| }) | ||
| ``` | ||
| - `matchFilter` (optional) - Filter matching any path that should be loaded. Can be a RegExp, a string, or a function returning a boolean. | ||
| ### `matchFilter` (optional) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| matchFilter: (path) => path.split("/").at(-2) === "handlers" | ||
| }) | ||
| ``` | ||
| Filter matching any path that should be loaded. Can be a RegExp, a string, or a function returning a boolean. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| matchFilter: (path) => path.split("/").at(-2) === "handlers" | ||
| }) | ||
| ``` | ||
| - `ignoreFilter` (optional) - Filter matching any path that should not be loaded. Can be a RegExp, a string ,or a function returning a boolean. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| ignoreFilter: (path) => path.endsWith('.spec.js') | ||
| }) | ||
| ``` | ||
| ### `ignoreFilter` (optional) | ||
| Filter matching any path that should not be loaded. Can be a RegExp, a string ,or a function returning a boolean. | ||
| - `ignorePattern` (optional) - RegExp matching any file or folder that should not be loaded. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| ignoreFilter: (path) => path.endsWith('.spec.js') | ||
| }) | ||
| ``` | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| ignorePattern: /^.*(?:test|spec).js$/ | ||
| }) | ||
| ``` | ||
| ### `ignorePattern` (optional) | ||
| RegExp matching any file or folder that should not be loaded. | ||
| - `scriptPattern` (optional) - Regex to override the script files accepted by default. You should only use this option | ||
| with a [customization hooks](https://nodejs.org/docs/latest/api/module.html#customization-hooks) | ||
| provider, such as `ts-node`. Otherwise, widening the acceptance extension here will result in an error. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| ignorePattern: /^.*(?:test|spec).js$/ | ||
| }) | ||
| ``` | ||
| ### `scriptPattern` (optional) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| scriptPattern: /(?<!\.d)\.(ts|tsx)$/ | ||
| }) | ||
| ``` | ||
| Regex to override the script files accepted by default. You should only use this option with a [customization hooks](https://nodejs.org/docs/latest/api/module.html#customization-hooks)provider, such as `ts-node`. Otherwise, widening the acceptance extension here will result in an error. | ||
| - `indexPattern` (optional) - Regex to override the `index.js` naming convention | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| indexPattern: /^.*routes(?:\.ts|\.js|\.cjs|\.mjs)$/ | ||
| }) | ||
| ``` | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| scriptPattern: /(?<!\.d)\.(ts|tsx)$/ | ||
| }) | ||
| ``` | ||
| - `maxDepth` (optional) - Limits the depth at which nested plugins are loaded | ||
| ### `indexPattern` (optional) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| maxDepth: 2 // files in `opts.dir` nested more than 2 directories deep will be ignored. | ||
| }) | ||
| ``` | ||
| Regex to override the `index.js` naming convention. | ||
| - `forceESM` (optional) - If set to 'true' it always use `await import` to load plugins or hooks. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| indexPattern: /^.*routes(?:\.ts|\.js|\.cjs|\.mjs)$/ | ||
| }) | ||
| ``` | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| forceESM: true | ||
| }) | ||
| ``` | ||
| - `encapsulate` (optional) - Defaults to 'true', if set to 'false' each plugin loaded is wrapped with [fastify-plugin](https://github.com/fastify/fastify-plugin). This allows you to share contexts between plugins and the parent context if needed. For example, if you need to share decorators. Read [this](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md#sharing-between-contexts) for more details. | ||
| ### `maxDepth` (optional) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| encapsulate: false | ||
| }) | ||
| ``` | ||
| Limits the depth at which nested plugins are loaded. | ||
| - `options` (optional) - Global options object used for all registered plugins | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| maxDepth: 2 // files in `opts.dir` nested more than 2 directories deep will be ignored. | ||
| }) | ||
| ``` | ||
| Any option specified here will override `plugin.autoConfig` options specified in the plugin itself. | ||
| ### `forceESM` (optional) | ||
| When setting both `options.prefix` and `plugin.autoPrefix` they will be concatenated. | ||
| If set to 'true' it always use `await import` to load plugins or hooks. | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| forceESM: true | ||
| }) | ||
| ``` | ||
| // /plugins/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| ### `encapsulate` (optional) - Default: `true` | ||
| module.exports.autoPrefix = '/something' | ||
| If set to `false` each plugin loaded is wrapped with [fastify-plugin](https://github.com/fastify/fastify-plugin). This allows you to share contexts between plugins and the parent context if needed. For example, if you need to share decorators. Read [this](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md#sharing-between-contexts) for more details. | ||
| // /plugins/something.mjs | ||
| export default function (f, opts, next) { | ||
| f.get('/', (request, reply) => { | ||
| reply.send({ something: 'else' }) | ||
| }) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| encapsulate: false | ||
| }) | ||
| ``` | ||
| next() | ||
| } | ||
| ### `options` (optional) | ||
| export const autoPrefix = '/prefixed' | ||
| Global options object used for all registered plugins. | ||
| // routes can now be added to /defaultPrefix/something | ||
| ``` | ||
| Any option specified here will override `plugin.autoConfig` options specified in the plugin itself. | ||
| - `autoHooks` (optional) - Apply hooks from `autohooks.js` file(s) to plugins found in folder | ||
| When setting both `options.prefix` and `plugin.autoPrefix` they will be concatenated. | ||
| Automatic hooks from `autohooks` files will be encapsulated with plugins. If `false`, all `autohooks.js` files will be ignored. | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true // apply hooks to routes in this level | ||
| // /plugins/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| module.exports.autoPrefix = '/something' | ||
| // /plugins/something.mjs | ||
| export default function (f, opts, next) { | ||
| f.get('/', (request, reply) => { | ||
| reply.send({ something: 'else' }) | ||
| }) | ||
| ``` | ||
| If `autoHooks` is set, all plugins in the folder will be [encapsulated](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md) | ||
| and decorated values _will not be exported_ outside the folder. | ||
| next() | ||
| } | ||
| - `autoHooksPattern` (optional) - Regex to override the `autohooks` naming convention | ||
| export const autoPrefix = '/prefixed' | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, | ||
| autoHooksPattern: /^[_.]?auto_?hooks(?:\.js|\.cjs|\.mjs)$/i | ||
| }) | ||
| ``` | ||
| // routes can now be added to /defaultPrefix/something | ||
| ``` | ||
| - `cascadeHooks` (optional) - If using `autoHooks`, cascade hooks to all children. Ignored if `autoHooks` is `false`. | ||
| ### `autoHooks` (optional) | ||
| Default behavior of `autoHooks` is to apply hooks only to the level on which the `autohooks.js` file is found. Setting `cascadeHooks: true` will continue applying the hooks to any children. | ||
| Apply hooks from `autohooks.js` file(s) to plugins found in folder. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, // apply hooks to routes in this level, | ||
| cascadeHooks: true // continue applying hooks to children, starting at this level | ||
| }) | ||
| ``` | ||
| Automatic hooks from `autohooks` files will be encapsulated with plugins. If `false`, all `autohooks.js` files will be ignored. | ||
| - `overwriteHooks` (optional) - If using `cascadeHooks`, cascade will be reset when a new `autohooks.js` file is encountered. Ignored if `autoHooks` is `false`. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true // apply hooks to routes in this level | ||
| }) | ||
| ``` | ||
| Default behavior of `cascadeHooks` is to accumulate hooks as new `autohooks.js` files are discovered and cascade to children. Setting `overwriteHooks: true` will start a new hook cascade when new `autohooks.js` files are encountered. | ||
| If `autoHooks` is set, all plugins in the folder will be [encapsulated](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md) | ||
| and decorated values _will not be exported_ outside the folder. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, // apply hooks to routes in this level, | ||
| cascadeHooks: true, // continue applying hooks to children, starting at this level, | ||
| overwriteHooks: true // re-start hook cascade when a new `autohooks.js` file is found | ||
| }) | ||
| ``` | ||
| ### `autoHooksPattern` (optional) | ||
| - `routeParams` (optional) - Folders prefixed with `_` will be turned into route parameters. | ||
| Regex to override the `autohooks` naming convention. | ||
| If you want to use mixed route parameters use a double underscore `__`. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, | ||
| autoHooksPattern: /^[_.]?auto_?hooks(?:\.js|\.cjs|\.mjs)$/i | ||
| }) | ||
| ``` | ||
| ```js | ||
| /* | ||
| ├── routes | ||
| ├── __country-__language | ||
| │ │ └── actions.js | ||
| │ └── users | ||
| │ ├── _id | ||
| │ │ └── actions.js | ||
| │ ├── __country-__language | ||
| │ │ └── actions.js | ||
| │ └── index.js | ||
| └── app.js | ||
| */ | ||
| ### `cascadeHooks` (optional) | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| routeParams: true | ||
| // routes/users/_id/actions.js will be loaded with prefix /users/:id | ||
| // routes/__country-__language/actions.js will be loaded with prefix /:country-:language | ||
| }) | ||
| If using `autoHooks`, cascade hooks to all children. Ignored if `autoHooks` is `false`. | ||
| // curl http://localhost:3000/users/index | ||
| // { userIndex: [ { id: 7, username: 'example' } ] } | ||
| Default behavior of `autoHooks` is to apply hooks only to the level on which the `autohooks.js` file is found. Setting `cascadeHooks: true` will continue applying the hooks to any children. | ||
| // curl http://localhost:3000/users/7/details | ||
| // { user: { id: 7, username: 'example' } } | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, // apply hooks to routes in this level, | ||
| cascadeHooks: true // continue applying hooks to children, starting at this level | ||
| }) | ||
| ``` | ||
| // curl http://localhost:3000/be-nl | ||
| // { country: 'be', language: 'nl' } | ||
| ``` | ||
| ### `overwriteHooks` (optional) | ||
| If using `cascadeHooks`, cascade will be reset when a new `autohooks.js` file is encountered. Ignored if `autoHooks` is `false`. | ||
| Default behavior of `cascadeHooks` is to accumulate hooks as new `autohooks.js` files are discovered and cascade to children. Setting `overwriteHooks: true` will start a new hook cascade when new `autohooks.js` files are encountered. | ||
| ```js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| autoHooks: true, // apply hooks to routes in this level, | ||
| cascadeHooks: true, // continue applying hooks to children, starting at this level, | ||
| overwriteHooks: true // re-start hook cascade when a new `autohooks.js` file is found | ||
| }) | ||
| ``` | ||
| ### `routeParams` (optional) | ||
| Folders prefixed with `_` will be turned into dynamic route parameters. If you want to use mixed route parameters use a double underscore `__`. | ||
| ```js | ||
| /* | ||
| ├── routes | ||
| ├── __country-__language | ||
| │ │ └── actions.js | ||
| │ └── users | ||
| │ ├── _id | ||
| │ │ └── actions.js | ||
| │ ├── __country-__language | ||
| │ │ └── actions.js | ||
| │ └── index.js | ||
| └── app.js | ||
| */ | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'routes'), | ||
| routeParams: true | ||
| // routes/users/_id/actions.js will be loaded with prefix /users/:id | ||
| // routes/__country-__language/actions.js will be loaded with prefix /:country-:language | ||
| }) | ||
| // curl http://localhost:3000/users/index | ||
| // { userIndex: [ { id: 7, username: 'example' } ] } | ||
| // curl http://localhost:3000/users/7/details | ||
| // { user: { id: 7, username: 'example' } } | ||
| // curl http://localhost:3000/be-nl | ||
| // { country: 'be', language: 'nl' } | ||
| ``` | ||
| ## Override TypeScript detection using an environment variable | ||
| This plugin uses [native type stripping](https://nodejs.org/docs/latest-v23.x/api/typescript.html#modules-typescript) with Node 23 and later. | ||
@@ -339,259 +367,270 @@ | ||
| - `plugin.autoConfig` - Specifies the options to be used as the `opts` parameter. | ||
| ### `plugin.autoConfig` | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| console.log(opts.foo) // 'bar' | ||
| next() | ||
| } | ||
| Specifies the options to be used as the `opts` parameter. | ||
| module.exports.autoConfig = { foo: 'bar' } | ||
| ``` | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| console.log(opts.foo) // 'bar' | ||
| next() | ||
| } | ||
| Or with ESM syntax: | ||
| module.exports.autoConfig = { foo: 'bar' } | ||
| ``` | ||
| ```js | ||
| import plugin from '../lib-plugin.js' | ||
| Or with ESM syntax: | ||
| export default async function myPlugin (app, options) { | ||
| app.get('/', async (request, reply) => { | ||
| return { hello: options.name } | ||
| }) | ||
| } | ||
| export const autoConfig = { name: 'y' } | ||
| ``` | ||
| ```js | ||
| import plugin from '../lib-plugin.js' | ||
| You can also use a callback function if you need to access the parent instance: | ||
| ```js | ||
| export const autoConfig = (fastify) => { | ||
| return { name: 'y ' + fastify.rootName } | ||
| } | ||
| ``` | ||
| export default async function myPlugin (app, options) { | ||
| app.get('/', async (request, reply) => { | ||
| return { hello: options.name } | ||
| }) | ||
| } | ||
| export const autoConfig = { name: 'y' } | ||
| ``` | ||
| However, note that the `prefix` option should be set directly on `autoConfig` for autoloading to work as expected: | ||
| ```js | ||
| export const autoConfig = (fastify) => { | ||
| return { name: 'y ' + fastify.rootName } | ||
| } | ||
| You can also use a callback function if you need to access the parent instance: | ||
| ```js | ||
| export const autoConfig = (fastify) => { | ||
| return { name: 'y ' + fastify.rootName } | ||
| } | ||
| ``` | ||
| autoConfig.prefix = '/hello' | ||
| ``` | ||
| However, note that the `prefix` option should be set directly on `autoConfig` for autoloading to work as expected: | ||
| ```js | ||
| export const autoConfig = (fastify) => { | ||
| return { name: 'y ' + fastify.rootName } | ||
| } | ||
| - `plugin.autoPrefix` - Set routing prefix for plugin | ||
| autoConfig.prefix = '/hello' | ||
| ``` | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| fastify.get('/', (request, reply) => { | ||
| reply.send({ hello: 'world' }) | ||
| }) | ||
| ### `plugin.autoPrefix` | ||
| next() | ||
| } | ||
| Set routing prefix for plugin. | ||
| module.exports.autoPrefix = '/something' | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| fastify.get('/', (request, reply) => { | ||
| reply.send({ hello: 'world' }) | ||
| }) | ||
| // when loaded with autoload, this will be exposed as /something | ||
| ``` | ||
| next() | ||
| } | ||
| Or with ESM syntax: | ||
| module.exports.autoPrefix = '/something' | ||
| ```js | ||
| export default async function (app, opts) { | ||
| app.get('/', (request, reply) => { | ||
| return { something: 'else' } | ||
| }) | ||
| } | ||
| // when loaded with autoload, this will be exposed as /something | ||
| ``` | ||
| export const autoPrefix = '/prefixed' | ||
| ``` | ||
| Or with ESM syntax: | ||
| ```js | ||
| export default async function (app, opts) { | ||
| app.get('/', (request, reply) => { | ||
| return { something: 'else' } | ||
| }) | ||
| } | ||
| - `plugin.prefixOverride` - Override all other prefix options | ||
| export const autoPrefix = '/prefixed' | ||
| ``` | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| ### `plugin.prefixOverride` | ||
| // /foo/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| Override all other prefix options. | ||
| module.exports.prefixOverride = '/overriddenPrefix' | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| // this will be exposed as /overriddenPrefix | ||
| ``` | ||
| // /foo/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| Or with ESM syntax: | ||
| module.exports.prefixOverride = '/overriddenPrefix' | ||
| ```js | ||
| export default async function (app, opts) { | ||
| // your plugin | ||
| } | ||
| // this will be exposed as /overriddenPrefix | ||
| ``` | ||
| export const prefixOverride = '/overriddenPrefix' | ||
| ``` | ||
| Or with ESM syntax: | ||
| If you have a plugin in the folder you do not want any prefix applied to, you can set `prefixOverride = ''`: | ||
| ```js | ||
| export default async function (app, opts) { | ||
| // your plugin | ||
| } | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| export const prefixOverride = '/overriddenPrefix' | ||
| ``` | ||
| // /foo/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| If you have a plugin in the folder you do not want any prefix applied to, you can set `prefixOverride = ''`: | ||
| // optional | ||
| module.exports.prefixOverride = '' | ||
| ```js | ||
| // index.js | ||
| fastify.register(autoLoad, { | ||
| dir: path.join(__dirname, 'plugins'), | ||
| options: { prefix: '/defaultPrefix' } | ||
| }) | ||
| // routes can now be added without a prefix | ||
| ``` | ||
| // /foo/something.js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| - `plugin.autoload` - Toggle whether the plugin should be loaded | ||
| // optional | ||
| module.exports.prefixOverride = '' | ||
| Example: | ||
| // routes can now be added without a prefix | ||
| ``` | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| ### `plugin.autoload` | ||
| // optional | ||
| module.exports.autoload = false | ||
| ``` | ||
| Toggle whether the plugin should be loaded. | ||
| - `opts.name` - Set name of plugin so that it can be referenced as a dependency | ||
| Example: | ||
| - `opts.dependencies` - Set plugin dependencies to ensure correct load order | ||
| ```js | ||
| module.exports = function (fastify, opts, next) { | ||
| // your plugin | ||
| } | ||
| Example: | ||
| // optional | ||
| module.exports.autoload = false | ||
| ``` | ||
| ```js | ||
| // plugins/plugin-a.js | ||
| const fp = require('fastify-plugin') | ||
| ### `opts.name` | ||
| function plugin (fastify, opts, next) { | ||
| // plugin a | ||
| } | ||
| Set name of plugin so that it can be referenced as a dependency. | ||
| module.exports = fp(plugin, { | ||
| name: 'plugin-a', | ||
| dependencies: ['plugin-b'] | ||
| }) | ||
| ### `opts.dependencies` | ||
| // plugins/plugin-b.js | ||
| function plugin (fastify, opts, next) { | ||
| // plugin b | ||
| } | ||
| Set plugin dependencies to ensure correct load order. | ||
| module.exports = fp(plugin, { | ||
| name: 'plugin-b' | ||
| }) | ||
| ``` | ||
| Example: | ||
| ## Autohooks: | ||
| ```js | ||
| // plugins/plugin-a.js | ||
| const fp = require('fastify-plugin') | ||
| The autohooks functionality provides several options for automatically adding hooks, decorators, etc. to your routes. CJS and ESM `autohook` formats are supported. | ||
| function plugin (fastify, opts, next) { | ||
| // plugin a | ||
| } | ||
| The default behavior of `autoHooks: true` is to encapsulate the `autohooks.js` plugin with the contents of the folder containing the file. The `cascadeHooks: true` option encapsulates the hooks with the current folder contents and all subsequent children, with any additional `autohooks.js` files being applied cumulatively. The `overwriteHooks: true` option will restart the cascade any time an `autohooks.js` file is encountered. | ||
| module.exports = fp(plugin, { | ||
| name: 'plugin-a', | ||
| dependencies: ['plugin-b'] | ||
| }) | ||
| Plugins and hooks are encapsulated together by folder and registered on the `fastify` instance that loaded the `@fastify/autoload` plugin. For more information on how encapsulation works in Fastify, see: https://fastify.dev/docs/latest/Reference/Encapsulation/#encapsulation | ||
| // plugins/plugin-b.js | ||
| function plugin (fastify, opts, next) { | ||
| // plugin b | ||
| } | ||
| ### Example: | ||
| module.exports = fp(plugin, { | ||
| name: 'plugin-b' | ||
| }) | ||
| ``` | ||
| ``` | ||
| ├── plugins | ||
| │ ├── hooked-plugin | ||
| │ │ ├── autohooks.js // req.hookOne = 'yes' # CJS syntax | ||
| │ │ ├── routes.js | ||
| │ │ └── children | ||
| │ │ ├── old-routes.js | ||
| │ │ ├── new-routes.js | ||
| │ │ └── grandchildren | ||
| │ │ ├── autohooks.mjs // req.hookTwo = 'yes' # ESM syntax | ||
| │ │ └── routes.mjs | ||
| │ └── standard-plugin | ||
| │ └── routes.js | ||
| └── app.js | ||
| ``` | ||
| ## Autohooks: | ||
| ```js | ||
| // hooked-plugin/autohooks.js | ||
| The autohooks functionality provides several options for automatically adding hooks, decorators, etc. to your routes. CJS and ESM `autohook` formats are supported. | ||
| module.exports = async function (app, opts) { | ||
| app.addHook('onRequest', async (req, reply) => { | ||
| req.hookOne = yes; | ||
| }); | ||
| } | ||
| The default behavior of `autoHooks: true` is to encapsulate the `autohooks.js` plugin with the contents of the folder containing the file. The `cascadeHooks: true` option encapsulates the hooks with the current folder contents and all subsequent children, with any additional `autohooks.js` files being applied cumulatively. The `overwriteHooks: true` option will restart the cascade any time an `autohooks.js` file is encountered. | ||
| // hooked-plugin/children/grandchildren/autohooks.mjs | ||
| Plugins and hooks are encapsulated together by folder and registered on the `fastify` instance that loaded the `@fastify/autoload` plugin. For more information on how encapsulation works in Fastify, see: https://fastify.dev/docs/latest/Reference/Encapsulation/#encapsulation | ||
| export default async function (app, opts) { | ||
| app.addHook('onRequest', async (req, reply) => { | ||
| req.hookTwo = yes | ||
| }) | ||
| } | ||
| ``` | ||
| ### Example: | ||
| ```bash | ||
| # app.js { autoHooks: true } | ||
| ``` | ||
| ├── plugins | ||
| │ ├── hooked-plugin | ||
| │ │ ├── autohooks.js // req.hookOne = 'yes' # CJS syntax | ||
| │ │ ├── routes.js | ||
| │ │ └── children | ||
| │ │ ├── old-routes.js | ||
| │ │ ├── new-routes.js | ||
| │ │ └── grandchildren | ||
| │ │ ├── autohooks.mjs // req.hookTwo = 'yes' # ESM syntax | ||
| │ │ └── routes.mjs | ||
| │ └── standard-plugin | ||
| │ └── routes.js | ||
| └── app.js | ||
| ``` | ||
| $ curl http://localhost:3000/standard-plugin/ | ||
| {} # no hooks in this folder, so behavior is unchanged | ||
| ```js | ||
| // hooked-plugin/autohooks.js | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| module.exports = async function (app, opts) { | ||
| app.addHook('onRequest', async (req, reply) => { | ||
| req.hookOne = yes; | ||
| }); | ||
| } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| {} | ||
| // hooked-plugin/children/grandchildren/autohooks.mjs | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| {} | ||
| export default async function (app, opts) { | ||
| app.addHook('onRequest', async (req, reply) => { | ||
| req.hookTwo = yes | ||
| }) | ||
| } | ||
| ``` | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookTwo: 'yes' } | ||
| ``` | ||
| ```bash | ||
| # app.js { autoHooks: true } | ||
| ```bash | ||
| # app.js { autoHooks: true, cascadeHooks: true } | ||
| $ curl http://localhost:3000/standard-plugin/ | ||
| {} # no hooks in this folder, so behavior is unchanged | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| {} | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| {} | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookOne: 'yes', hookTwo: 'yes' } # hooks are accumulated and applied in ascending order | ||
| ``` | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookTwo: 'yes' } | ||
| ``` | ||
| ```bash | ||
| # app.js { autoHooks: true, cascadeHooks: true, overwriteHooks: true } | ||
| ```bash | ||
| # app.js { autoHooks: true, cascadeHooks: true } | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookTwo: 'yes' } # new autohooks.js takes over | ||
| ``` | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookOne: 'yes', hookTwo: 'yes' } # hooks are accumulated and applied in ascending order | ||
| ``` | ||
| ```bash | ||
| # app.js { autoHooks: true, cascadeHooks: true, overwriteHooks: true } | ||
| $ curl http://localhost:3000/hooked-plugin/ | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/old | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/new | ||
| { hookOne: 'yes' } | ||
| $ curl http://localhost:3000/hooked-plugin/children/grandchildren/ | ||
| { hookTwo: 'yes' } # new autohooks.js takes over | ||
| ``` | ||
| ## License | ||
| Licensed under [MIT](./LICENSE). |
@@ -7,3 +7,4 @@ 'use strict' | ||
| 'node', | ||
| '--require=@swc-node/register', | ||
| '-r', | ||
| '@swc-node/register', | ||
| 'test/typescript/basic.ts' | ||
@@ -10,0 +11,0 @@ ] |
@@ -1,40 +0,33 @@ | ||
| 'use script' | ||
| 'use strict' | ||
| const { test: t } = require('node:test') | ||
| const { test: testRunner } = require('node:test') | ||
| const assert = require('node:assert/strict') | ||
| const fastify = require('fastify') | ||
| const basicApp = require('./basic/app.ts') | ||
| t.plan(5) | ||
| testRunner('integration test with fastify autoload', async (t: any) => { | ||
| const app = fastify() | ||
| app.register(basicApp) | ||
| const app = fastify() | ||
| await app.ready() | ||
| app.register(basicApp) | ||
| app.ready(async function (err) { | ||
| t.error(err) | ||
| await app | ||
| .inject({ | ||
| await t.test('should return javascript data', async () => { | ||
| const res = await app.inject({ | ||
| url: '/javascript', | ||
| }) | ||
| .then(function (res: any) { | ||
| t.equal(res.statusCode, 200) | ||
| t.same(JSON.parse(res.payload), { script: 'java' }) | ||
| }) | ||
| .catch((err) => { | ||
| t.error(err) | ||
| }) | ||
| await app | ||
| .inject({ | ||
| assert.strictEqual(res.statusCode, 200) | ||
| assert.deepStrictEqual(res.json(), { script: 'java' }) | ||
| }) | ||
| await t.test('should return typescript data', async () => { | ||
| const res = await app.inject({ | ||
| url: '/typescript', | ||
| }) | ||
| .then(function (res: any) { | ||
| t.equal(res.statusCode, 200) | ||
| t.same(JSON.parse(res.payload), { script: 'type' }) | ||
| }) | ||
| .catch((err) => { | ||
| t.error(err) | ||
| }) | ||
| assert.strictEqual(res.statusCode, 200) | ||
| assert.deepStrictEqual(res.json(), { script: 'type' }) | ||
| }) | ||
| await app.close() | ||
| }) |
@@ -1,2 +0,2 @@ | ||
| import test, { describe, before, after } from 'node:test' | ||
| import { describe, test } from 'node:test' | ||
| import assert from 'node:assert' | ||
@@ -13,16 +13,13 @@ import fastify from 'fastify' | ||
| const app = fastify() | ||
| before(async function () { | ||
| test('should load routes and respond correctly', async function () { | ||
| app.register(fastifyAutoLoad, { dir: resolve(__dirname, 'app'), forceESM: true }) | ||
| await app.ready() | ||
| }) | ||
| after(async function () { | ||
| await app.close() | ||
| }) | ||
| test('should load routes and respond correctly', async function () { | ||
| const res = await app.inject({ url: '/installed' }) | ||
| assert.strictEqual(res.statusCode, 200) | ||
| assert.deepStrictEqual(JSON.parse(res.payload), { result: 'ok' }) | ||
| await app.close() | ||
| }) | ||
| }) |
@@ -10,5 +10,7 @@ import fastify from 'fastify' | ||
| }, function (err) { | ||
| if (err) process.stderr.write('failed') | ||
| if (err) { | ||
| process.stderr.write('failed') | ||
| } | ||
| process.stdout.write('success') | ||
| app.close() | ||
| }) |
| import { exec } from 'node:child_process' | ||
| import { join } from 'node:path' | ||
| describe('integration test', function () { | ||
| const isWindows = process.platform === 'win32' | ||
| test.concurrent.each(['ts-node', 'ts-node-dev'])( | ||
@@ -8,3 +11,24 @@ 'integration with %s', | ||
| await new Promise(function (resolve) { | ||
| const child = exec(`${instance} "${process.cwd()}/test/typescript-jest/integration/instance.ts"`) | ||
| const compilerOpts = JSON.stringify({ | ||
| module: 'commonjs', | ||
| moduleResolution: 'node', | ||
| esModuleInterop: true | ||
| }) | ||
| const optionsArg = isWindows | ||
| ? `"${compilerOpts.replace(/"/g, '\\"')}"` | ||
| : `'${compilerOpts}'` | ||
| const filePath = join( | ||
| process.cwd(), | ||
| 'test', | ||
| 'typescript-jest', | ||
| 'integration', | ||
| 'instance.ts' | ||
| ) | ||
| const child = exec( | ||
| `npx ${instance} --compiler-options ${optionsArg} "${filePath}"` | ||
| ) | ||
| let stderr = '' | ||
@@ -18,2 +42,3 @@ child.stderr?.on('data', function (b) { | ||
| }) | ||
| child.once('close', function () { | ||
@@ -26,4 +51,4 @@ expect(stderr.includes('failed')).toStrictEqual(false) | ||
| }, | ||
| 30000 | ||
| isWindows ? 60000 : 30000 | ||
| ) | ||
| }) |
| # Number of days of inactivity before an issue becomes stale | ||
| daysUntilStale: 15 | ||
| # Number of days of inactivity before a stale issue is closed | ||
| daysUntilClose: 7 | ||
| # Issues with these labels will never be considered stale | ||
| exemptLabels: | ||
| - "discussion" | ||
| - "feature request" | ||
| - "bug" | ||
| - "help wanted" | ||
| - "plugin suggestion" | ||
| - "good first issue" | ||
| # Label to use when marking an issue as stale | ||
| staleLabel: stale | ||
| # Comment to post when marking an issue as stale. Set to `false` to disable | ||
| markComment: > | ||
| This issue has been automatically marked as stale because it has not had | ||
| recent activity. It will be closed if no further activity occurs. Thank you | ||
| for your contributions. | ||
| # Comment to post when closing a stale issue. Set to `false` to disable | ||
| closeComment: false |
| 'use strict' | ||
| const { exec } = require('node:child_process') | ||
| const args = [ | ||
| 'TSIMP_PROJECT=tsconfig.tsimp.json', | ||
| 'node', | ||
| '--import=tsimp/import', | ||
| 'test/typescript/basic.ts' | ||
| ] | ||
| const child = exec(args.join(' '), { | ||
| shell: true | ||
| }) | ||
| child.stdout.pipe(process.stdout) | ||
| child.stderr.pipe(process.stderr) | ||
| child.once('close', process.exit) |
| module.exports = async (fastify) => { | ||
| fastify.get('/', function () { | ||
| return { foo: 'bar' } | ||
| }) | ||
| } |
| module.exports = async (fastify) => { | ||
| fastify.get('/', function () { | ||
| return { hello: 'world' } | ||
| }) | ||
| } |
| import fastify, { FastifyInstance, FastifyPluginCallback } from 'fastify' | ||
| import { expectType } from 'tsd' | ||
| import * as fastifyAutoloadStar from '..' | ||
| import fastifyAutoloadDefault, { AutoloadPluginOptions, fastifyAutoload as fastifyAutoloadNamed } from '..' | ||
| import fastifyAutoloadCjsImport = require('..') | ||
| const fastifyAutoloadCjs = require('..') | ||
| const app: FastifyInstance = fastify() | ||
| app.register(fastifyAutoloadNamed, { dir: 'test' }) | ||
| app.register(fastifyAutoloadDefault, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjs, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjsImport.default, { dir: 'test' }) | ||
| app.register(fastifyAutoloadCjsImport.fastifyAutoload, { dir: 'test' }) | ||
| app.register(fastifyAutoloadStar.default, { dir: 'test' }) | ||
| app.register(fastifyAutoloadStar.fastifyAutoload, { dir: 'test' }) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadNamed) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadDefault) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadCjsImport.default) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadCjsImport.fastifyAutoload) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadStar.default) | ||
| expectType<FastifyPluginCallback<AutoloadPluginOptions>>(fastifyAutoloadStar.fastifyAutoload) | ||
| expectType<any>(fastifyAutoloadCjs) | ||
| const opt1: AutoloadPluginOptions = { | ||
| dir: 'test' | ||
| } | ||
| const opt2: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignorePattern: /skip/ | ||
| } | ||
| const opt3: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| scriptPattern: /js/, | ||
| indexPattern: /index/, | ||
| } | ||
| const opt4: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| options: { | ||
| prefix: 'test' | ||
| } | ||
| } | ||
| const opt5: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| maxDepth: 1, | ||
| } | ||
| const opt6: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| routeParams: true, | ||
| } | ||
| const opt7: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| forceESM: true, | ||
| autoHooks: true, | ||
| autoHooksPattern: /^[_.]?auto_?hooks(?:\.ts|\.js|\.cjs|\.mjs)$/i, | ||
| cascadeHooks: true, | ||
| overwriteHooks: true, | ||
| } | ||
| const opt8: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| encapsulate: false, | ||
| } | ||
| const opt9: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: /test/, | ||
| matchFilter: /handler/ | ||
| } | ||
| const opt10: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: 'test', | ||
| matchFilter: 'handler' | ||
| } | ||
| const opt11: AutoloadPluginOptions = { | ||
| dir: 'test', | ||
| ignoreFilter: (path) => path.endsWith('.spec.ts'), | ||
| matchFilter: (path) => path.split('/').at(-2) === 'handlers' | ||
| } | ||
| app.register(fastifyAutoloadDefault, opt1) | ||
| app.register(fastifyAutoloadDefault, opt2) | ||
| app.register(fastifyAutoloadDefault, opt3) | ||
| app.register(fastifyAutoloadDefault, opt4) | ||
| app.register(fastifyAutoloadDefault, opt5) | ||
| app.register(fastifyAutoloadDefault, opt6) | ||
| app.register(fastifyAutoloadDefault, opt7) | ||
| app.register(fastifyAutoloadDefault, opt8) | ||
| app.register(fastifyAutoloadDefault, opt9) | ||
| app.register(fastifyAutoloadDefault, opt10) | ||
| app.register(fastifyAutoloadDefault, opt11) |
Potential vulnerability
Supply chain riskInitial human review suggests the presence of a vulnerability in this package. It is pending further analysis and confirmation.
Potential vulnerability
Supply chain riskInitial human review suggests the presence of a vulnerability in this package. It is pending further analysis and confirmation.
182140
4.19%23
-8%298
2.41%4815
4.09%635
6.54%12
20%10
11.11%