@gasket/plugin-fastify 8.0.0-next.1 → 8.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,12 +24,22 @@ export default makeGasket({
24
24
  });
25
25
  ```
26
26
 
27
+ ## Fastify Version Support
28
+
29
+ This plugin supports both Fastify v4 and v5 through an internal adapter pattern. The appropriate version-specific adapter is automatically selected based on your installed Fastify version.
30
+
31
+ ### Fastify v4
32
+ - **Installation**: `npm install fastify@^4`
33
+
34
+ ### Fastify v5
35
+ - **Installation**: `npm install fastify@^5`
36
+
37
+ The plugin automatically detects your Fastify version and applies the correct configuration. No manual configuration is required.
38
+
27
39
  ## Configuration
28
40
 
29
41
  All the configurations for the plugin are added under `fastify` in the config:
30
42
 
31
- - `compression`: true by default. Can be set to false if applying compression
32
- differently.
33
43
  - `trustProxy`: Enable trust proxy option, [see Fastify documentation for possible values](https://fastify.dev/docs/latest/Reference/Server/#trustproxy)
34
44
  - `disableRequestLogging`: Turn off request logging, true by default
35
45
 
@@ -41,8 +51,6 @@ export default makeGasket({
41
51
  pluginFastify
42
52
  ],
43
53
  fastify: {
44
- compression: false,
45
- excludedRoutesRegex: /^(?!\/_next\/)/,
46
54
  trustProxy: true
47
55
  }
48
56
  });
@@ -70,8 +78,7 @@ export default {
70
78
 
71
79
  ### fastify
72
80
 
73
- Executed **after** the `middleware` event for when you need full control over
74
- the `fastify` instance.
81
+ Used to add routes and middleware directly to the `fastify` instance.
75
82
 
76
83
  ```js
77
84
  export default {
@@ -82,7 +89,6 @@ export default {
82
89
  *
83
90
  * @param {Gasket} gasket The Gasket API
84
91
  * @param {Fastify} fastify Fastify app instance
85
- * @returns {function|function[]} middleware(s)
86
92
  */
87
93
  fastify: async function (gasket, fastify) {
88
94
  }
@@ -92,20 +98,24 @@ export default {
92
98
 
93
99
  ### errorMiddleware
94
100
 
95
- Executed after the `fastify` event. All middleware functions returned from this
96
- hook will be applied to Fastify.
101
+ Executed after the `fastify` event. All error handler functions returned from this
102
+ hook will be applied to Fastify in order.
97
103
 
98
104
  ```js
99
105
  export default {
100
106
  name: 'sample-plugin',
101
107
  hooks: {
102
108
  /**
103
- * Add Fastify error middlewares
109
+ * Add error handling for Fastify
104
110
  *
105
111
  * @param {Gasket} gasket The Gasket API
106
- * @returns {function|function[]} error middleware(s)
112
+ * @returns {function|function[]} error handler(s)
107
113
  */
108
114
  errorMiddleware: function (gasket) {
115
+ return function (err, req, res, next) {
116
+ // handle error or pass to next handler
117
+ next(err);
118
+ };
109
119
  }
110
120
  }
111
121
  };
@@ -0,0 +1,46 @@
1
+ import { createRequire } from 'module';
2
+ import { FastifyV4Adapter } from './fastify-v4-adapter.js';
3
+ import { FastifyV5Adapter } from './fastify-v5-adapter.js';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ /**
8
+ * Detect the installed Fastify version.
9
+ * Reads the version from fastify/package.json in node_modules.
10
+ * This works reliably with npm, pnpm, yarn, and bun.
11
+ * @returns {string} - Version string (e.g., '5.2.1')
12
+ */
13
+ export function detectFastifyVersion() {
14
+ try {
15
+ const fastifyModule = require('fastify/package.json');
16
+ return fastifyModule.version;
17
+ } catch (err) {
18
+ throw new Error(
19
+ 'Could not detect Fastify version. Ensure fastify is installed.\n' +
20
+ `Error: ${err.message}`
21
+ );
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Create the appropriate Fastify adapter based on the installed Fastify version.
27
+ * Automatically detects the version from fastify/package.json.
28
+ * @returns {import('./base-adapter.js').FastifyAdapter} - Adapter instance
29
+ */
30
+ export function createFastifyAdapter() {
31
+ const version = detectFastifyVersion();
32
+ const majorVersion = parseInt(version.split('.')[0], 10);
33
+
34
+ switch (majorVersion) {
35
+ case 4:
36
+ return new FastifyV4Adapter();
37
+ case 5:
38
+ return new FastifyV5Adapter();
39
+ default:
40
+ throw new Error(
41
+ `Unsupported Fastify version: ${version}\n` +
42
+ 'Gasket supports Fastify v4 and v5. Please upgrade or downgrade Fastify, ' +
43
+ 'or open an issue at https://github.com/godaddy/gasket'
44
+ );
45
+ }
46
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Base abstract class for Fastify version adapters.
3
+ * Provides a common interface for creating and configuring Fastify instances
4
+ * across different major versions.
5
+ * @abstract
6
+ */
7
+ export class FastifyAdapter {
8
+ constructor() {
9
+ if (new.target === FastifyAdapter) {
10
+ throw new Error('FastifyAdapter is an abstract class and cannot be instantiated directly');
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Create a Fastify instance with version-specific configuration.
16
+ * Subclasses must implement this method to handle version-specific options.
17
+ * @abstract
18
+ * @param {object} config - Fastify configuration options
19
+ * @param {boolean} [config.trustProxy] - Trust proxy headers
20
+ * @param {object} [config.https] - HTTPS configuration
21
+ * @param {boolean} [config.http2] - Enable HTTP/2
22
+ * @param {boolean} [config.disableRequestLogging] - Disable request logging
23
+ * @param {object} logger - Gasket logger instance
24
+ * @returns {ReturnType<typeof import('fastify').default>} - Configured Fastify instance
25
+ */
26
+ // eslint-disable-next-line no-unused-vars
27
+ createInstance(config, logger) {
28
+ throw new Error('createInstance() must be implemented by subclass');
29
+ }
30
+ }
@@ -0,0 +1,55 @@
1
+ import fastify from 'fastify';
2
+ import { FastifyAdapter } from './base-adapter.js';
3
+
4
+ const fallbackMap = {
5
+ fatal: 'error',
6
+ trace: 'debug'
7
+ };
8
+
9
+ /**
10
+ * Adapter for Fastify v4.x
11
+ *
12
+ * Key differences from v5:
13
+ * - Uses 'logger' option (not 'loggerInstance')
14
+ * - No 'useSemicolonDelimiter' option
15
+ * - Supports Node.js v14+
16
+ */
17
+ export class FastifyV4Adapter extends FastifyAdapter {
18
+ /**
19
+ * Align Gasket logger with Fastify v4 requirements.
20
+ * Ensures 'fatal' and 'trace' log levels exist.
21
+ * @param {object} logger - Gasket logger instance
22
+ * @returns {object} - Fastify-compatible logger
23
+ */
24
+ alignLogger(logger) {
25
+ const fastifyLogger = logger;
26
+ ['fatal', 'trace'].forEach(level => {
27
+ if (!logger[level]) {
28
+ fastifyLogger[level] = logger[fallbackMap[level]];
29
+ }
30
+ });
31
+ return fastifyLogger;
32
+ }
33
+
34
+ /**
35
+ * Create a Fastify v4 instance.
36
+ * @param {object} config - Fastify configuration
37
+ * @param {object} logger - Gasket logger
38
+ * @returns {ReturnType<typeof import('fastify').default>} - Configured Fastify v4 instance
39
+ */
40
+ createInstance(config, logger) {
41
+ const { trustProxy = false, disableRequestLogging = true, https, http2, ...restConfig } = config;
42
+
43
+ const fastifyLogger = this.alignLogger(logger);
44
+
45
+ // Fastify v4 uses 'logger' option
46
+ return fastify({
47
+ logger: fastifyLogger,
48
+ trustProxy,
49
+ disableRequestLogging,
50
+ https,
51
+ http2,
52
+ ...restConfig
53
+ });
54
+ }
55
+ }
@@ -0,0 +1,67 @@
1
+ import fastify from 'fastify';
2
+ import { FastifyAdapter } from './base-adapter.js';
3
+
4
+ const fallbackMap = {
5
+ fatal: 'error',
6
+ trace: 'debug'
7
+ };
8
+
9
+ /**
10
+ * Adapter for Fastify v5.x
11
+ *
12
+ * Key differences from v4:
13
+ * - Uses 'loggerInstance' option (not 'logger')
14
+ * - Adds 'useSemicolonDelimiter' option (defaults to true)
15
+ * - Requires Node.js v20+
16
+ * - Stricter JSON Schema validation
17
+ */
18
+ export class FastifyV5Adapter extends FastifyAdapter {
19
+ /**
20
+ * Align Gasket logger with Fastify v5 requirements.
21
+ * Ensures 'fatal' and 'trace' log levels exist.
22
+ * @param {object} logger - Gasket logger instance
23
+ * @returns {object} - Fastify-compatible logger
24
+ */
25
+ alignLogger(logger) {
26
+ const fastifyLogger = logger;
27
+ ['fatal', 'trace'].forEach(level => {
28
+ if (!logger[level]) {
29
+ fastifyLogger[level] = logger[fallbackMap[level]];
30
+ }
31
+ });
32
+ return fastifyLogger;
33
+ }
34
+
35
+ /**
36
+ * Create a Fastify v5 instance.
37
+ * @param {object} config - Fastify configuration
38
+ * @param {object} logger - Gasket logger
39
+ * @returns {ReturnType<typeof import('fastify').default>} - Configured Fastify v5 instance
40
+ */
41
+ createInstance(config, logger) {
42
+ const {
43
+ trustProxy = false,
44
+ disableRequestLogging = true,
45
+ https,
46
+ http2,
47
+ useSemicolonDelimiter = true,
48
+ ...restConfig
49
+ } = config;
50
+
51
+ const fastifyLogger = this.alignLogger(logger);
52
+
53
+ // Fastify v5 uses 'loggerInstance' instead of 'logger'
54
+ // Router options are nested under 'routerOptions' to avoid deprecation warnings
55
+ return fastify({
56
+ loggerInstance: fastifyLogger,
57
+ trustProxy,
58
+ disableRequestLogging,
59
+ https,
60
+ http2,
61
+ routerOptions: {
62
+ useSemicolonDelimiter
63
+ },
64
+ ...restConfig
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,4 @@
1
+ export { FastifyAdapter } from './base-adapter.js';
2
+ export { FastifyV4Adapter } from './fastify-v4-adapter.js';
3
+ export { FastifyV5Adapter } from './fastify-v5-adapter.js';
4
+ export { createFastifyAdapter, detectFastifyVersion } from './adapter-factory.js';
@@ -5,23 +5,33 @@ import { getAppInstance } from './utils.js';
5
5
 
6
6
  /**
7
7
  * Create the Fastify instance and setup the lifecycle hooks.
8
- * Fastify is compatible with express middleware out of the box, so we can
9
- * use the same middleware lifecycles.
10
8
  * @type {import('@gasket/core').HookHandler<'createServers'>}
11
9
  */
12
10
  export default async function createServers(gasket, serverOpts) {
13
- /** Cast to Fastify + Express hybrid because Gasket adds `.use()` via @fastify/express plugin */
14
- const app = /** @type {import('fastify').FastifyInstance & { use: Function }} */ (getAppInstance(gasket));
11
+ const app = getAppInstance(gasket);
15
12
 
16
13
  // allow consuming apps to directly append options to their server
17
14
  await gasket.exec('fastify', app);
18
15
 
19
16
  const postRenderingStacks = (await gasket.exec('errorMiddleware')).filter(Boolean);
20
- postRenderingStacks.forEach((stack) => {
21
- /** @type {import('connect').NextHandleFunction} */
22
- const middleware = stack;
23
- app.use(middleware);
24
- });
17
+ if (postRenderingStacks.length) {
18
+ // Fastify only supports one setErrorHandler per scope, so all Express-style
19
+ // error handlers (err, req, res, next) are chained manually through a single
20
+ // handler. request.raw and reply.raw expose the underlying Node.js
21
+ // IncomingMessage and ServerResponse that Express-style handlers expect.
22
+ app.setErrorHandler((error, request, reply) => {
23
+ let index = 0;
24
+ const next = (err) => {
25
+ const current = err || error;
26
+ if (index < postRenderingStacks.length) {
27
+ postRenderingStacks[index++](current, request.raw, reply.raw, next);
28
+ } else if (!reply.sent) {
29
+ reply.send(current);
30
+ }
31
+ };
32
+ next(error);
33
+ });
34
+ }
25
35
 
26
36
  return {
27
37
  ...serverOpts,
package/lib/index.d.ts CHANGED
@@ -2,8 +2,6 @@ import type { MaybeAsync, MaybeMultiple, Plugin } from '@gasket/core';
2
2
  import type { Logger } from '@gasket/plugin-logger';
3
3
  import type {
4
4
  FastifyInstance,
5
- FastifyRequest,
6
- FastifyReply,
7
5
  FastifyServerOptions,
8
6
  FastifyBaseLogger,
9
7
  FastifyTypeProviderDefault,
@@ -12,8 +10,6 @@ import type {
12
10
  import type { IncomingMessage, ServerResponse } from 'http';
13
11
 
14
12
  export interface FastifyConfig {
15
- /** Enable compression */
16
- compression?: boolean;
17
13
  /** Trust proxy configuration */
18
14
  trustProxy?: FastifyServerOptions['trustProxy'];
19
15
  /** Fastify request logging per route */
@@ -36,11 +32,11 @@ declare module '@gasket/core' {
36
32
  }
37
33
 
38
34
 
39
- /** Error handler function type */
35
+ /** Error handler function type (Express-style, receives raw Node.js req/res) */
40
36
  type ErrorHandler = (
41
37
  error: Error,
42
- req: FastifyRequest,
43
- res: FastifyReply,
38
+ req: IncomingMessage,
39
+ res: ServerResponse,
44
40
  next: (error?: Error) => void
45
41
  ) => void;
46
42
 
package/lib/index.js CHANGED
@@ -31,8 +31,7 @@ const plugin = {
31
31
  method: 'exec',
32
32
  description: 'Modify the Fastify instance to for adding endpoints',
33
33
  link: 'README.md#express',
34
- parent: 'createServers',
35
- after: 'middleware'
34
+ parent: 'createServers'
36
35
  },
37
36
  {
38
37
  name: 'errorMiddleware',
@@ -50,19 +49,6 @@ const plugin = {
50
49
  link: 'README.md#configuration',
51
50
  description: 'Fastify configuration object',
52
51
  type: 'object'
53
- },
54
- {
55
- name: 'fastify.compression',
56
- link: 'README.md#configuration',
57
- description: 'Automatic compression',
58
- type: 'boolean',
59
- default: true
60
- },
61
- {
62
- name: 'fastify.excludedRoutesRegex',
63
- link: 'README.md#configuration',
64
- description: 'Routes to be excluded based on a regex',
65
- type: 'RegExp'
66
52
  }
67
53
  ]
68
54
  };
package/lib/utils.js CHANGED
@@ -1,29 +1,9 @@
1
1
  // @ts-nocheck
2
2
 
3
- import fastify from 'fastify';
4
-
5
- const fallbackMap = {
6
- fatal: 'error',
7
- trace: 'debug'
8
- };
9
-
10
- /**
11
- * A helper function to ensure that all the required log levels
12
- * for a Fastify server are present.
13
- * @type {import('@gasket/plugin-fastify').alignLogger}
14
- */
15
- function alignLogger(logger) {
16
- const fastifyLogger = logger;
17
- ['fatal', 'trace'].map(level => {
18
- if (!logger[level]) {
19
- fastifyLogger[level] = logger[fallbackMap[level]];
20
- }
21
- });
22
-
23
- return fastifyLogger;
24
- }
3
+ import { createFastifyAdapter } from './adapters/index.js';
25
4
 
26
5
  let instance = null;
6
+ let adapter = null;
27
7
 
28
8
  /**
29
9
  * Get the Fastify instance.
@@ -33,21 +13,29 @@ let instance = null;
33
13
  export function getAppInstance(gasket) {
34
14
  if (!instance) {
35
15
  const { fastify: fastifyConfig = {}, http2, https } = gasket.config;
36
- const { trustProxy = false, disableRequestLogging = true } = fastifyConfig;
37
- const fastifyLogger = alignLogger(gasket.logger);
38
16
 
39
- instance = fastify({ logger: fastifyLogger, trustProxy, https, http2, disableRequestLogging });
17
+ // Create the appropriate adapter for the installed Fastify version
18
+ adapter = createFastifyAdapter();
19
+
20
+ // Use the adapter to create the Fastify instance
21
+ instance = adapter.createInstance(
22
+ {
23
+ ...fastifyConfig,
24
+ https,
25
+ http2
26
+ },
27
+ gasket.logger
28
+ );
40
29
  }
41
30
 
42
31
  return instance;
43
32
  }
44
33
 
45
- export { alignLogger };
46
-
47
34
  /**
48
35
  * Clear the Fastify app instance (for testing only)
49
36
  * @returns {void}
50
37
  */
51
38
  export function testClearAppInstance() {
52
39
  instance = null;
40
+ adapter = null;
53
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gasket/plugin-fastify",
3
- "version": "8.0.0-next.1",
3
+ "version": "8.0.0-next.3",
4
4
  "description": "Adds fastify support to your application",
5
5
  "type": "module",
6
6
  "types": "./lib/index.d.ts",
@@ -16,7 +16,10 @@
16
16
  },
17
17
  "./package.json": "./package.json"
18
18
  },
19
- "repository": "godaddy/gasket.git",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "godaddy/gasket.git"
22
+ },
20
23
  "publishConfig": {
21
24
  "access": "public"
22
25
  },
@@ -29,22 +32,18 @@
29
32
  "license": "MIT",
30
33
  "bugs": "https://github.com/godaddy/gasket/issues",
31
34
  "homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-plugin-fastify",
32
- "dependencies": {
33
- "@fastify/express": "^3.0.0",
34
- "fastify": "^4.29.1"
35
- },
36
35
  "devDependencies": {
37
36
  "@types/connect": "^3.4.38",
38
- "fastify": "^4.29.1",
37
+ "fastify": "^5.0.0",
39
38
  "vitest": "^3.2.0",
40
- "@gasket/core": "^8.0.0-next.1",
39
+ "@gasket/core": "^8.0.0-next.2",
41
40
  "@gasket/plugin-https": "^8.0.0-next.1",
42
41
  "@gasket/plugin-logger": "^8.0.0-next.1",
43
42
  "@gasket/plugin-metadata": "^8.0.0-next.1",
44
- "create-gasket-app": "^8.0.0-next.1"
43
+ "create-gasket-app": "^8.0.0-next.2"
45
44
  },
46
45
  "peerDependencies": {
47
- "fastify": "^4.29.1"
46
+ "fastify": "^4.29.1 || ^5.0.0"
48
47
  },
49
48
  "scripts": {
50
49
  "lint": "eslint .",