From 330dc8d59f24f2f9f933f3c7251300b117b4302c Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 15:23:57 +0545 Subject: [PATCH 01/20] chore: create stub package for error handler --- packages/errorHandler/.eslintignore | 4 + packages/errorHandler/.eslintrc.cjs | 4 + packages/errorHandler/.gitignore | 4 + packages/errorHandler/README.md | 48 +++++++++ packages/errorHandler/package.json | 62 ++++++++++++ packages/errorHandler/src/index.ts | 18 ++++ packages/errorHandler/src/parse.ts | 24 +++++ packages/errorHandler/src/plugin.ts | 23 +++++ packages/errorHandler/src/types.ts | 27 ++++++ packages/errorHandler/tsconfig.json | 9 ++ packages/errorHandler/vite.config.ts | 43 ++++++++ pnpm-lock.yaml | 140 +++++++++++++++------------ 12 files changed, 346 insertions(+), 60 deletions(-) create mode 100644 packages/errorHandler/.eslintignore create mode 100644 packages/errorHandler/.eslintrc.cjs create mode 100644 packages/errorHandler/.gitignore create mode 100644 packages/errorHandler/README.md create mode 100644 packages/errorHandler/package.json create mode 100644 packages/errorHandler/src/index.ts create mode 100644 packages/errorHandler/src/parse.ts create mode 100644 packages/errorHandler/src/plugin.ts create mode 100644 packages/errorHandler/src/types.ts create mode 100644 packages/errorHandler/tsconfig.json create mode 100644 packages/errorHandler/vite.config.ts diff --git a/packages/errorHandler/.eslintignore b/packages/errorHandler/.eslintignore new file mode 100644 index 000000000..7c8131f7e --- /dev/null +++ b/packages/errorHandler/.eslintignore @@ -0,0 +1,4 @@ +.eslintrc.cjs +coverage +dist +node_modules diff --git a/packages/errorHandler/.eslintrc.cjs b/packages/errorHandler/.eslintrc.cjs new file mode 100644 index 000000000..7de245ade --- /dev/null +++ b/packages/errorHandler/.eslintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["@prefabs.tech/eslint-config/fastify"], +}; diff --git a/packages/errorHandler/.gitignore b/packages/errorHandler/.gitignore new file mode 100644 index 000000000..62853f374 --- /dev/null +++ b/packages/errorHandler/.gitignore @@ -0,0 +1,4 @@ +**/*.log* +/coverage +/dist +/node_modules diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md new file mode 100644 index 000000000..755511bd2 --- /dev/null +++ b/packages/errorHandler/README.md @@ -0,0 +1,48 @@ +# @prefabs.tech/fastify-error-handler + +A [Fastify](https://github.com/fastify/fastify) plugin that defines an opinionated config for an API. + +When registered on a Fastify instance, the plugin will: + +* decorate the Fastify instance with the `errorHandler` object, available with the `errorHandler` attribute. +* decorate all requests with the `errorHandler` object, available with the `errorHandler` attribute; this can be used to construct a `buildContext` for mercurius resolvers, for example. +* decorate the Fastify instance with a `hostname` attribute. + +## Installation + +Install with npm: + +```bash +npm install @prefabs.tech/fastify-error-handler +``` + +Install with pnpm: + +```bash +pnpm add --filter "@scope/project @prefabs.tech/fastify-error-handler +``` + +## Usage + +import configPlugin from "@prefabs.tech/fastify-error-handler"; +import Fastify from "fastify"; + +import config from "./config"; + +const start = async () => { + // Create fastify instance + const fastify = Fastify({ + logger: config.logger, + }); + + // Register fastify-error-handler plugin + await fastify.register(configPlugin, { config }); + + await fastify.listen({ + port: config.port, + host: "0.0.0.0", + }); +}; + +start(); +``` diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json new file mode 100644 index 000000000..94ae2243e --- /dev/null +++ b/packages/errorHandler/package.json @@ -0,0 +1,62 @@ +{ + "name": "@prefabs.tech/fastify-error-handler", + "version": "0.88.1", + "description": "Fastify error-handler plugin", + "homepage": "https://github.com/prefabs-tech/fastify/tree/main/packages/error-handler#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/prefabs-tech/fastify.git", + "directory": "packages/errorHandler" + }, + "license": "MIT", + "type": "module", + "exports": { + ".": { + "import": "./dist/prefabs-tech-fastify-error-handler.js", + "require": "./dist/prefabs-tech-fastify-error-handler.umd.cjs" + } + }, + "main": "./dist/prefabs-tech-fastify-error-handler.umd.cjs", + "module": "./dist/prefabs-tech-fastify-error-handler.js", + "types": "./dist/types/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "vite build && tsc --emitDeclarationOnly && mv dist/src dist/types", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "sort-package": "npx sort-package-json", + "typecheck": "tsc --noEmit -p tsconfig.json --composite false" + }, + "devDependencies": { + "@prefabs.tech/eslint-config": "0.2.0", + "@prefabs.tech/tsconfig": "0.2.0", + "@types/node": "20.19.9", + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", + "@vitest/coverage-istanbul": "3.2.4", + "eslint": "8.57.1", + "eslint-config-prettier": "9.1.2", + "eslint-import-resolver-alias": "1.1.2", + "eslint-import-resolver-typescript": "3.10.1", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-n": "14.0.0", + "eslint-plugin-prettier": "5.5.3", + "eslint-plugin-promise": "7.2.1", + "eslint-plugin-unicorn": "56.0.1", + "fastify": "5.4.0", + "fastify-plugin": "5.0.1", + "prettier": "3.6.2", + "typescript": "5.8.3", + "vite": "6.3.5", + "vitest": "3.2.4" + }, + "peerDependencies": { + "fastify": ">=5.2.1", + "fastify-plugin": ">=5.0.1" + }, + "engines": { + "node": ">=20" + } +} diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts new file mode 100644 index 000000000..3e4ee33b1 --- /dev/null +++ b/packages/errorHandler/src/index.ts @@ -0,0 +1,18 @@ +import type { ApiConfig } from "./types"; + +declare module "fastify" { + interface FastifyInstance { + config: ApiConfig; + hostname: string; + } + + interface FastifyRequest { + config: ApiConfig; + } +} + +export { default } from "./plugin"; + +export { default as parse } from "./parse"; + +export type { ApiConfig, AppConfig } from "./types"; diff --git a/packages/errorHandler/src/parse.ts b/packages/errorHandler/src/parse.ts new file mode 100644 index 000000000..f4e809184 --- /dev/null +++ b/packages/errorHandler/src/parse.ts @@ -0,0 +1,24 @@ +const parse = ( + value: string | undefined, + fallback: boolean | number | string | undefined, +) => { + if (value === undefined) { + return fallback; + } + + switch (typeof fallback) { + case "boolean": { + return !!JSON.parse(value); + } + + case "number": { + return JSON.parse(value); + } + + default: { + return value; + } + } +}; + +export default parse; diff --git a/packages/errorHandler/src/plugin.ts b/packages/errorHandler/src/plugin.ts new file mode 100644 index 000000000..ef71d0fc3 --- /dev/null +++ b/packages/errorHandler/src/plugin.ts @@ -0,0 +1,23 @@ +import FastifyPlugin from "fastify-plugin"; + +import type { ApiConfig } from "./types"; +import type { FastifyInstance, FastifyRequest } from "fastify"; + +const plugin = async ( + fastify: FastifyInstance, + options: { config: ApiConfig }, +) => { + const config = options.config; + + // Decorate api and request with `config` + fastify.decorate("config", config); + fastify.addHook("onRequest", async (request: FastifyRequest) => { + request.config = config; + }); + + const { baseUrl, port } = config; + const host = `${baseUrl}:${port}`; + fastify.decorate("hostname", host); +}; + +export default FastifyPlugin(plugin); diff --git a/packages/errorHandler/src/types.ts b/packages/errorHandler/src/types.ts new file mode 100644 index 000000000..d6f78a08c --- /dev/null +++ b/packages/errorHandler/src/types.ts @@ -0,0 +1,27 @@ +interface AppConfig { + id: number; + name: string; + origin: string; + supportedRoles: string[]; +} + +interface ApiConfig { + appName: string; + appOrigin: string[]; + apps?: AppConfig[]; + baseUrl: string; + env: string; + name: string; + pagination?: { + default_limit: number; + max_limit: number; + }; + port: number; + protocol: string; + rest: { + enabled: boolean; + }; + version: string; +} + +export type { ApiConfig, AppConfig }; diff --git a/packages/errorHandler/tsconfig.json b/packages/errorHandler/tsconfig.json new file mode 100644 index 000000000..50005d55b --- /dev/null +++ b/packages/errorHandler/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@prefabs.tech/tsconfig/fastify.json", + "compilerOptions": { + "outDir": "./dist", + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/errorHandler/vite.config.ts b/packages/errorHandler/vite.config.ts new file mode 100644 index 000000000..e41613280 --- /dev/null +++ b/packages/errorHandler/vite.config.ts @@ -0,0 +1,43 @@ +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { defineConfig, loadEnv } from "vite"; + +import { peerDependencies } from "./package.json"; + +// https://vitejs.dev/config/ +export default defineConfig(({ mode }) => { + process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; + + return { + build: { + lib: { + entry: resolve(dirname(fileURLToPath(import.meta.url)), "src/index.ts"), + fileName: "prefabs-tech-fastify-error-handler", + name: "PrefabsTechFastifyErrorHandler", + }, + rollupOptions: { + external: Object.keys(peerDependencies), + output: { + exports: "named", + globals: { + fastify: "Fastify", + "fastify-plugin": "FastifyPlugin", + }, + }, + }, + target: "es2022", + }, + resolve: { + alias: { + "@/": new URL("src/", import.meta.url).pathname, + }, + }, + test: { + coverage: { + provider: "istanbul", + reporter: ["text", "json", "html"], + }, + }, + }; +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67509ce1d..04652f3d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,72 @@ importers: specifier: 3.2.4 version: 3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1) + packages/errorHandler: + devDependencies: + '@prefabs.tech/eslint-config': + specifier: 0.2.0 + version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) + '@prefabs.tech/tsconfig': + specifier: 0.2.0 + version: 0.2.0(@types/node@20.19.9) + '@types/node': + specifier: 20.19.9 + version: 20.19.9 + '@typescript-eslint/eslint-plugin': + specifier: 8.38.0 + version: 8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': + specifier: 8.38.0 + version: 8.38.0(eslint@8.57.1)(typescript@5.8.3) + '@vitest/coverage-istanbul': + specifier: 3.2.4 + version: 3.2.4(vitest@3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1)) + eslint: + specifier: 8.57.1 + version: 8.57.1 + eslint-config-prettier: + specifier: 9.1.2 + version: 9.1.2(eslint@8.57.1) + eslint-import-resolver-alias: + specifier: 1.1.2 + version: 1.1.2(eslint-plugin-import@2.32.0) + eslint-import-resolver-typescript: + specifier: 3.10.1 + version: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + eslint-plugin-import: + specifier: 2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-n: + specifier: 14.0.0 + version: 14.0.0(eslint@8.57.1) + eslint-plugin-prettier: + specifier: 5.5.3 + version: 5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) + eslint-plugin-promise: + specifier: 7.2.1 + version: 7.2.1(eslint@8.57.1) + eslint-plugin-unicorn: + specifier: 56.0.1 + version: 56.0.1(eslint@8.57.1) + fastify: + specifier: 5.4.0 + version: 5.4.0 + fastify-plugin: + specifier: 5.0.1 + version: 5.0.1 + prettier: + specifier: 3.6.2 + version: 3.6.2 + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: 6.3.5 + version: 6.3.5(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1) + vitest: + specifier: 3.2.4 + version: 3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1) + packages/firebase: dependencies: firebase-admin: @@ -1257,12 +1323,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2797,15 +2857,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -3802,10 +3853,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -4442,9 +4489,6 @@ packages: resolution: {integrity: sha512-8RGlznQx/Nb1xC3xKUFXHWov7pn7JdH++YVwlr6SLT6k3ft1h+ImGqZdVudbdKruFckIq9wheq9s4hgCivJDow==} engines: {node: '>=16'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5609,10 +5653,6 @@ packages: tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -6852,11 +6892,6 @@ snapshots: '@esbuild/win32-x64@0.25.4': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -6867,7 +6902,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -7101,7 +7136,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -8751,10 +8786,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.3.7: dependencies: ms: 2.1.3 @@ -9152,7 +9183,7 @@ snapshots: eslint-plugin-es: 4.1.0(eslint@8.57.1) eslint-utils: 3.0.0(eslint@8.57.1) ignore: 5.3.2 - is-core-module: 2.15.1 + is-core-module: 2.16.1 minimatch: 3.1.2 resolve: 1.22.8 semver: 6.3.1 @@ -9177,13 +9208,13 @@ snapshots: eslint-plugin-promise@7.2.1(eslint@8.57.1): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) eslint: 8.57.1 eslint-plugin-unicorn@56.0.1(eslint@8.57.1): dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) ci-info: 4.0.0 clean-regexp: 1.0.0 core-js-compat: 3.38.1 @@ -9197,7 +9228,7 @@ snapshots: read-pkg-up: 7.0.1 regexp-tree: 0.1.27 regjsparser: 0.10.0 - semver: 7.6.3 + semver: 7.7.2 strip-indent: 3.0.0 eslint-scope@7.2.2: @@ -9224,7 +9255,7 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 @@ -9235,7 +9266,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.6 + debug: 4.4.1 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -9391,7 +9422,7 @@ snapshots: process-warning: 5.0.0 rfdc: 1.4.1 secure-json-parse: 4.0.0 - semver: 7.6.3 + semver: 7.7.2 toad-cache: 3.7.0 fastparallel@2.4.1: @@ -10023,10 +10054,6 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.15.1: - dependencies: - hasown: 2.0.2 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -10279,7 +10306,7 @@ snapshots: jws: 3.2.2 lodash: 4.17.21 ms: 2.1.3 - semver: 7.6.3 + semver: 7.7.2 juice@10.0.0: dependencies: @@ -10308,7 +10335,7 @@ snapshots: dependencies: '@types/express': 4.17.21 '@types/jsonwebtoken': 9.0.5 - debug: 4.4.0 + debug: 4.4.1 jose: 4.15.4 limiter: 1.1.5 lru-memoizer: 2.2.0 @@ -10869,8 +10896,6 @@ snapshots: fastparallel: 2.4.1 qlobber: 8.0.1 - ms@2.1.2: {} - ms@2.1.3: {} mustache@4.2.0: {} @@ -11275,7 +11300,7 @@ snapshots: process-warning: 3.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 - safe-stable-stringify: 2.4.3 + safe-stable-stringify: 2.5.0 sonic-boom: 3.8.1 thread-stream: 2.7.0 @@ -11529,7 +11554,7 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -12083,11 +12108,6 @@ snapshots: tinyexec@1.0.1: {} - tinyglobby@0.2.13: - dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.14: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -12344,7 +12364,7 @@ snapshots: picomatch: 4.0.2 postcss: 8.5.3 rollup: 4.41.0 - tinyglobby: 0.2.13 + tinyglobby: 0.2.14 optionalDependencies: '@types/node': 20.19.9 fsevents: 2.3.3 From 6f1971ad5c7288ff885e96778e2a0862a59b4204 Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:10:09 +0545 Subject: [PATCH 02/20] feat: add types and remove unused files --- packages/errorHandler/src/parse.ts | 24 ---------------------- packages/errorHandler/src/types.ts | 32 ++++++++---------------------- 2 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 packages/errorHandler/src/parse.ts diff --git a/packages/errorHandler/src/parse.ts b/packages/errorHandler/src/parse.ts deleted file mode 100644 index f4e809184..000000000 --- a/packages/errorHandler/src/parse.ts +++ /dev/null @@ -1,24 +0,0 @@ -const parse = ( - value: string | undefined, - fallback: boolean | number | string | undefined, -) => { - if (value === undefined) { - return fallback; - } - - switch (typeof fallback) { - case "boolean": { - return !!JSON.parse(value); - } - - case "number": { - return JSON.parse(value); - } - - default: { - return value; - } - } -}; - -export default parse; diff --git a/packages/errorHandler/src/types.ts b/packages/errorHandler/src/types.ts index d6f78a08c..425dc467d 100644 --- a/packages/errorHandler/src/types.ts +++ b/packages/errorHandler/src/types.ts @@ -1,27 +1,11 @@ -interface AppConfig { - id: number; - name: string; - origin: string; - supportedRoles: string[]; -} +import { StackFrame } from "./httpError"; -interface ApiConfig { - appName: string; - appOrigin: string[]; - apps?: AppConfig[]; - baseUrl: string; - env: string; +type ErrorResponse = { + code: string; + message: string; name: string; - pagination?: { - default_limit: number; - max_limit: number; - }; - port: number; - protocol: string; - rest: { - enabled: boolean; - }; - version: string; -} + stack?: StackFrame[]; + statusCode: number; +}; -export type { ApiConfig, AppConfig }; +export type { ErrorResponse }; From 4987b23d7b4504353f102a4188ae4ae880006457 Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:11:33 +0545 Subject: [PATCH 03/20] feat: add fastify sensible and register it with error handler --- packages/errorHandler/package.json | 1 + packages/errorHandler/src/errorHandler.ts | 58 +++++++++++++++++++++++ packages/errorHandler/src/httpError.ts | 43 +++++++++++++++++ packages/errorHandler/src/plugin.ts | 25 ++++------ pnpm-lock.yaml | 47 ++++++++++++++++-- 5 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 packages/errorHandler/src/errorHandler.ts create mode 100644 packages/errorHandler/src/httpError.ts diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index 94ae2243e..b97ed085e 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -30,6 +30,7 @@ "typecheck": "tsc --noEmit -p tsconfig.json --composite false" }, "devDependencies": { + "@fastify/sensible": "6.0.3", "@prefabs.tech/eslint-config": "0.2.0", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/errorHandler/src/errorHandler.ts new file mode 100644 index 000000000..760710b83 --- /dev/null +++ b/packages/errorHandler/src/errorHandler.ts @@ -0,0 +1,58 @@ +import { HttpError } from "@fastify/sensible"; +import { FastifyReply, FastifyRequest } from "fastify"; + +import { parseStack, StackFrame } from "./httpError"; + +import type { ErrorResponse } from "./types"; + +export const errorHandler = ( + error: Error, + request: FastifyRequest, + reply: FastifyReply, +) => { + const { log: logger } = request; + + const isHttpError = error instanceof HttpError; + + if (isHttpError) { + const statusCode = error.statusCode || 500; + + if (statusCode >= 500) { + logger.error(error); + } else { + logger.info(error); + } + + const response: ErrorResponse = { + code: error.code ?? "INTERNAL_ERROR", + message: error.message, + name: error.name, + statusCode, + }; + + if (error.stack) { + response.stack = parseStack(error.stack); + } + + void reply.code(statusCode).send(response); + + return; + } + + // Unhandled error + logger.error(error); + + const response = { + code: "INTERNAL_ERROR", + message: error.message, + name: error.name, + stack: [] as StackFrame[], + statusCode: 500, + }; + + if (error.stack) { + response.stack = parseStack(error.stack); + } + + void reply.code(500).send(response); +}; diff --git a/packages/errorHandler/src/httpError.ts b/packages/errorHandler/src/httpError.ts new file mode 100644 index 000000000..164250449 --- /dev/null +++ b/packages/errorHandler/src/httpError.ts @@ -0,0 +1,43 @@ +export interface StackFrame { + columnNumber: number; + fileName: string; + functionName: string; + lineNumber: number; +} + +export const parseStack = (stack: string): StackFrame[] => { + // split stack into lines and remove the first line (error message) + const stackLines = stack.split("\n").slice(1); + + return stackLines + .map((line) => { + // match the stack trace line pattern + // Example: " at Object. (/app/src/controllers/workspace.ts:42:15)" + const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); + + if (match) { + return { + columnNumber: Number.parseInt(match[4], 10), + fileName: match[2].trim(), + functionName: match[1].trim(), + lineNumber: Number.parseInt(match[3], 10), + }; + } + + // Handle cases where the stack trace format is different + // Example: " at /app/src/controllers/workspace.ts:42:15" + const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/); + + if (simpleMatch) { + return { + columnNumber: Number.parseInt(simpleMatch[3], 10), + fileName: simpleMatch[1].trim(), + functionName: "anonymous", + lineNumber: Number.parseInt(simpleMatch[2], 10), + }; + } + + return; + }) + .filter((frame): frame is StackFrame => frame !== undefined); +}; diff --git a/packages/errorHandler/src/plugin.ts b/packages/errorHandler/src/plugin.ts index ef71d0fc3..30dfdb25a 100644 --- a/packages/errorHandler/src/plugin.ts +++ b/packages/errorHandler/src/plugin.ts @@ -1,23 +1,18 @@ +import fastifySensible from "@fastify/sensible"; import FastifyPlugin from "fastify-plugin"; -import type { ApiConfig } from "./types"; -import type { FastifyInstance, FastifyRequest } from "fastify"; +import { errorHandler } from "./errorHandler"; -const plugin = async ( - fastify: FastifyInstance, - options: { config: ApiConfig }, -) => { - const config = options.config; +import type { FastifyInstance } from "fastify"; - // Decorate api and request with `config` - fastify.decorate("config", config); - fastify.addHook("onRequest", async (request: FastifyRequest) => { - request.config = config; - }); +const plugin = async (fastify: FastifyInstance) => { + fastify.log.info("Registering fastify-error-handler plugin"); - const { baseUrl, port } = config; - const host = `${baseUrl}:${port}`; - fastify.decorate("hostname", host); + // const { config} = fastify; + + await fastify.register(fastifySensible); + + await fastify.setErrorHandler(errorHandler); }; export default FastifyPlugin(plugin); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04652f3d0..89d3442d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: packages/errorHandler: devDependencies: + '@fastify/sensible': + specifier: 6.0.3 + version: 6.0.3 '@prefabs.tech/eslint-config': specifier: 0.2.0 version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) @@ -1383,6 +1386,9 @@ packages: '@fastify/send@3.3.1': resolution: {integrity: sha512-6pofeVwaHN+E/MAofCwDqkWUliE3i++jlD0VH/LOfU8TJlCkMUSgKvA9bawDdVXxjve7XrdYMyDmkiYaoGWEtA==} + '@fastify/sensible@6.0.3': + resolution: {integrity: sha512-Iyn8698hp/e5+v8SNBBruTa7UfrMEP52R16dc9jMpqSyEcPsvWFQo+R6WwHCUnJiLIsuci2ZoEZ7ilrSSCPIVg==} + '@fastify/static@8.1.1': resolution: {integrity: sha512-TW9eyVHJLytZNpBlSIqd0bl1giJkEaRaPZG+5AT3L/OBKq9U8D7g/OYmc2NPQZnzPURGhMt3IAWuyVkvd2nOkQ==} @@ -3440,6 +3446,10 @@ packages: resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} engines: {node: '>= 0.12'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4284,6 +4294,10 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + mensch@0.3.4: resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} @@ -5776,6 +5790,10 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -5881,6 +5899,10 @@ packages: resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} engines: {node: '>= 0.10'} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -6974,6 +6996,16 @@ snapshots: http-errors: 2.0.0 mime: 3.0.0 + '@fastify/sensible@6.0.3': + dependencies: + '@lukeed/ms': 2.0.2 + dequal: 2.0.3 + fastify-plugin: 5.0.1 + forwarded: 0.2.0 + http-errors: 2.0.0 + type-is: 1.6.18 + vary: 1.1.2 + '@fastify/static@8.1.1': dependencies: '@fastify/accept-negotiator': 2.0.1 @@ -9529,6 +9561,8 @@ snapshots: mime-types: 2.1.35 optional: true + forwarded@0.2.0: {} + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -10488,6 +10522,8 @@ snapshots: math-intrinsics@1.1.0: {} + media-typer@0.3.0: {} + mensch@0.3.4: {} meow@12.1.1: {} @@ -10540,13 +10576,11 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 - mime-db@1.52.0: - optional: true + mime-db@1.52.0: {} mime-types@2.1.35: dependencies: mime-db: 1.52.0 - optional: true mime@2.6.0: {} @@ -12209,6 +12243,11 @@ snapshots: type-fest@0.8.1: {} + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -12336,6 +12375,8 @@ snapshots: validator@13.15.15: {} + vary@1.1.2: {} + vite-node@3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1): dependencies: cac: 6.7.14 From 46eb14c83c3785edd452ce595bf689bafb607ac4 Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:12:04 +0545 Subject: [PATCH 04/20] feat: export plugin with necessary types --- packages/errorHandler/src/index.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts index 3e4ee33b1..4737987a1 100644 --- a/packages/errorHandler/src/index.ts +++ b/packages/errorHandler/src/index.ts @@ -1,18 +1,11 @@ -import type { ApiConfig } from "./types"; +import type { HttpErrors } from "@fastify/sensible"; declare module "fastify" { interface FastifyInstance { - config: ApiConfig; - hostname: string; - } - - interface FastifyRequest { - config: ApiConfig; + httpErrors: HttpErrors; } } export { default } from "./plugin"; -export { default as parse } from "./parse"; - -export type { ApiConfig, AppConfig } from "./types"; +export type { ErrorResponse } from "./types"; From d27d9cc906bbd80653a0410b46ceea3882299876 Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:14:10 +0545 Subject: [PATCH 05/20] docs: update README --- packages/errorHandler/README.md | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md index 755511bd2..575f2301a 100644 --- a/packages/errorHandler/README.md +++ b/packages/errorHandler/README.md @@ -2,12 +2,6 @@ A [Fastify](https://github.com/fastify/fastify) plugin that defines an opinionated config for an API. -When registered on a Fastify instance, the plugin will: - -* decorate the Fastify instance with the `errorHandler` object, available with the `errorHandler` attribute. -* decorate all requests with the `errorHandler` object, available with the `errorHandler` attribute; this can be used to construct a `buildContext` for mercurius resolvers, for example. -* decorate the Fastify instance with a `hostname` attribute. - ## Installation Install with npm: @@ -23,26 +17,3 @@ pnpm add --filter "@scope/project @prefabs.tech/fastify-error-handler ``` ## Usage - -import configPlugin from "@prefabs.tech/fastify-error-handler"; -import Fastify from "fastify"; - -import config from "./config"; - -const start = async () => { - // Create fastify instance - const fastify = Fastify({ - logger: config.logger, - }); - - // Register fastify-error-handler plugin - await fastify.register(configPlugin, { config }); - - await fastify.listen({ - port: config.port, - host: "0.0.0.0", - }); -}; - -start(); -``` From 1db5e14ffcc0ffad2364f767008ef3c85c8475e9 Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:45:05 +0545 Subject: [PATCH 06/20] feat: add stackTrace config property --- packages/errorHandler/README.md | 4 ++++ packages/errorHandler/package.json | 1 + packages/errorHandler/src/index.ts | 10 ++++++++++ pnpm-lock.yaml | 3 +++ 4 files changed, 18 insertions(+) diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md index 575f2301a..36eeaf51a 100644 --- a/packages/errorHandler/README.md +++ b/packages/errorHandler/README.md @@ -2,6 +2,10 @@ A [Fastify](https://github.com/fastify/fastify) plugin that defines an opinionated config for an API. +## Requirements + +* [@prefabs.tech/fastify-config](../config/) + ## Installation Install with npm: diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index b97ed085e..af8a9b6cf 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -32,6 +32,7 @@ "devDependencies": { "@fastify/sensible": "6.0.3", "@prefabs.tech/eslint-config": "0.2.0", + "@prefabs.tech/fastify-config": "0.88.1", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", "@typescript-eslint/eslint-plugin": "8.38.0", diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts index 4737987a1..fadbf6bde 100644 --- a/packages/errorHandler/src/index.ts +++ b/packages/errorHandler/src/index.ts @@ -1,4 +1,6 @@ import type { HttpErrors } from "@fastify/sensible"; +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +import type { ApiConfig } from "@prefabs.tech/fastify-config"; declare module "fastify" { interface FastifyInstance { @@ -6,6 +8,14 @@ declare module "fastify" { } } +declare module "@prefabs.tech/fastify-config" { + interface ApiConfig { + errorHandler: { + stackTrace?: boolean; + }; + } +} + export { default } from "./plugin"; export type { ErrorResponse } from "./types"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89d3442d9..b8ff544e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,9 @@ importers: '@prefabs.tech/eslint-config': specifier: 0.2.0 version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) + '@prefabs.tech/fastify-config': + specifier: 0.88.1 + version: link:../config '@prefabs.tech/tsconfig': specifier: 0.2.0 version: 0.2.0(@types/node@20.19.9) From aa7daf9640bd0d9542197b6f5fb60d69778735bf Mon Sep 17 00:00:00 2001 From: anvesh Date: Tue, 29 Jul 2025 18:46:00 +0545 Subject: [PATCH 07/20] feat: check stack trace enabled to return stack with error --- packages/errorHandler/src/errorHandler.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/errorHandler/src/errorHandler.ts index 760710b83..6b4265d66 100644 --- a/packages/errorHandler/src/errorHandler.ts +++ b/packages/errorHandler/src/errorHandler.ts @@ -12,6 +12,9 @@ export const errorHandler = ( ) => { const { log: logger } = request; + const isStackTraceEnabled = + request.server.config.errorHandler?.stackTrace || false; + const isHttpError = error instanceof HttpError; if (isHttpError) { @@ -30,7 +33,7 @@ export const errorHandler = ( statusCode, }; - if (error.stack) { + if (isStackTraceEnabled && error.stack) { response.stack = parseStack(error.stack); } @@ -50,7 +53,7 @@ export const errorHandler = ( statusCode: 500, }; - if (error.stack) { + if (isStackTraceEnabled && error.stack) { response.stack = parseStack(error.stack); } From b945f99221cc6879c27ba89a55a89325e7a5ef4c Mon Sep 17 00:00:00 2001 From: anvesh Date: Wed, 30 Jul 2025 16:30:59 +0545 Subject: [PATCH 08/20] chore: remove unused comments --- packages/errorHandler/src/plugin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/errorHandler/src/plugin.ts b/packages/errorHandler/src/plugin.ts index 30dfdb25a..e71a6d55e 100644 --- a/packages/errorHandler/src/plugin.ts +++ b/packages/errorHandler/src/plugin.ts @@ -8,8 +8,6 @@ import type { FastifyInstance } from "fastify"; const plugin = async (fastify: FastifyInstance) => { fastify.log.info("Registering fastify-error-handler plugin"); - // const { config} = fastify; - await fastify.register(fastifySensible); await fastify.setErrorHandler(errorHandler); From 47af02ee40fe9384235fd31f33f597ba4377f2ec Mon Sep 17 00:00:00 2001 From: anvesh Date: Wed, 30 Jul 2025 16:32:51 +0545 Subject: [PATCH 09/20] docs: updated README --- packages/errorHandler/README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md index 36eeaf51a..e49d187af 100644 --- a/packages/errorHandler/README.md +++ b/packages/errorHandler/README.md @@ -1,10 +1,11 @@ # @prefabs.tech/fastify-error-handler -A [Fastify](https://github.com/fastify/fastify) plugin that defines an opinionated config for an API. +A [Fastify](https://github.com/fastify/fastify) plugin that provides an easy integration of error handler in fastify API. ## Requirements * [@prefabs.tech/fastify-config](../config/) +* [@fastify/sensible](https://github.com/fastify/fastify-sensible) ## Installation @@ -21,3 +22,31 @@ pnpm add --filter "@scope/project @prefabs.tech/fastify-error-handler ``` ## Usage + +### Register Plugin + +Register @prefabs.tech/fastify-error-handler package with your Fastify instance: + +```typescript +import errorHandlerPlugin from "@prefabs.tech/fastify-error-handler"; +import Fastify from "fastify"; + +import config from "./config"; + +const start = async () => { + // Create fastify instance + const fastify = Fastify({ + logger: config.logger, + }); + + // Register fastify-error-handler plugin + await fastify.register(errorHandlerPlugin); + + await fastify.listen({ + port: config.port, + host: "0.0.0.0", + }); +}; + +start(); +``` From 3d2fcc59b65dd5dc156e35b6758c31fdc5e72fa8 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Fri, 1 Aug 2025 16:53:55 +0545 Subject: [PATCH 10/20] chore: update version to 0.88.2 and add error handler dependency --- packages/errorHandler/package.json | 5 +++-- packages/errorHandler/src/errorHandler.ts | 4 ++-- packages/errorHandler/src/index.ts | 2 ++ packages/errorHandler/src/types.ts | 9 +++++++-- packages/errorHandler/src/{ => utils}/httpError.ts | 9 +-------- packages/user/package.json | 2 ++ pnpm-lock.yaml | 5 ++++- 7 files changed, 21 insertions(+), 15 deletions(-) rename packages/errorHandler/src/{ => utils}/httpError.ts (79%) diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index af8a9b6cf..de601a2be 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -1,6 +1,6 @@ { "name": "@prefabs.tech/fastify-error-handler", - "version": "0.88.1", + "version": "0.88.2", "description": "Fastify error-handler plugin", "homepage": "https://github.com/prefabs-tech/fastify/tree/main/packages/error-handler#readme", "repository": { @@ -32,7 +32,7 @@ "devDependencies": { "@fastify/sensible": "6.0.3", "@prefabs.tech/eslint-config": "0.2.0", - "@prefabs.tech/fastify-config": "0.88.1", + "@prefabs.tech/fastify-config": "0.88.2", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", "@typescript-eslint/eslint-plugin": "8.38.0", @@ -55,6 +55,7 @@ "vitest": "3.2.4" }, "peerDependencies": { + "@prefabs.tech/fastify-config": "0.88.2", "fastify": ">=5.2.1", "fastify-plugin": ">=5.0.1" }, diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/errorHandler/src/errorHandler.ts index 6b4265d66..66ae4d1ef 100644 --- a/packages/errorHandler/src/errorHandler.ts +++ b/packages/errorHandler/src/errorHandler.ts @@ -1,9 +1,9 @@ import { HttpError } from "@fastify/sensible"; import { FastifyReply, FastifyRequest } from "fastify"; -import { parseStack, StackFrame } from "./httpError"; +import { parseStack } from "./utils/httpError"; -import type { ErrorResponse } from "./types"; +import type { ErrorResponse, StackFrame } from "./types"; export const errorHandler = ( error: Error, diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts index fadbf6bde..0c2e54701 100644 --- a/packages/errorHandler/src/index.ts +++ b/packages/errorHandler/src/index.ts @@ -19,3 +19,5 @@ declare module "@prefabs.tech/fastify-config" { export { default } from "./plugin"; export type { ErrorResponse } from "./types"; + +export type { HttpErrors } from "@fastify/sensible"; diff --git a/packages/errorHandler/src/types.ts b/packages/errorHandler/src/types.ts index 425dc467d..47fd0a209 100644 --- a/packages/errorHandler/src/types.ts +++ b/packages/errorHandler/src/types.ts @@ -1,4 +1,9 @@ -import { StackFrame } from "./httpError"; +interface StackFrame { + columnNumber: number; + fileName: string; + functionName: string; + lineNumber: number; +} type ErrorResponse = { code: string; @@ -8,4 +13,4 @@ type ErrorResponse = { statusCode: number; }; -export type { ErrorResponse }; +export type { ErrorResponse, StackFrame }; diff --git a/packages/errorHandler/src/httpError.ts b/packages/errorHandler/src/utils/httpError.ts similarity index 79% rename from packages/errorHandler/src/httpError.ts rename to packages/errorHandler/src/utils/httpError.ts index 164250449..ea890e25d 100644 --- a/packages/errorHandler/src/httpError.ts +++ b/packages/errorHandler/src/utils/httpError.ts @@ -1,9 +1,4 @@ -export interface StackFrame { - columnNumber: number; - fileName: string; - functionName: string; - lineNumber: number; -} +import type { StackFrame } from "../types"; export const parseStack = (stack: string): StackFrame[] => { // split stack into lines and remove the first line (error message) @@ -12,7 +7,6 @@ export const parseStack = (stack: string): StackFrame[] => { return stackLines .map((line) => { // match the stack trace line pattern - // Example: " at Object. (/app/src/controllers/workspace.ts:42:15)" const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); if (match) { @@ -25,7 +19,6 @@ export const parseStack = (stack: string): StackFrame[] => { } // Handle cases where the stack trace format is different - // Example: " at /app/src/controllers/workspace.ts:42:15" const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/); if (simpleMatch) { diff --git a/packages/user/package.json b/packages/user/package.json index 15d0411d8..2fbcc8e58 100644 --- a/packages/user/package.json +++ b/packages/user/package.json @@ -37,6 +37,7 @@ "@fastify/formbody": "8.0.2", "@prefabs.tech/eslint-config": "0.2.0", "@prefabs.tech/fastify-config": "0.88.2", + "@prefabs.tech/fastify-error-handler": "0.88.2", "@prefabs.tech/fastify-graphql": "0.88.2", "@prefabs.tech/fastify-mailer": "0.88.2", "@prefabs.tech/fastify-s3": "0.88.2", @@ -74,6 +75,7 @@ "@fastify/cors": ">=11.0.1", "@fastify/formbody": ">=8.0.2", "@prefabs.tech/fastify-config": "0.88.2", + "@prefabs.tech/fastify-error-handler": "0.88.2", "@prefabs.tech/fastify-graphql": "0.88.2", "@prefabs.tech/fastify-mailer": "0.88.2", "@prefabs.tech/fastify-s3": "0.88.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7911fbee8..12bbf3430 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,7 +108,7 @@ importers: specifier: 0.2.0 version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) '@prefabs.tech/fastify-config': - specifier: 0.88.1 + specifier: 0.88.2 version: link:../config '@prefabs.tech/tsconfig': specifier: 0.2.0 @@ -762,6 +762,9 @@ importers: '@prefabs.tech/fastify-config': specifier: 0.88.2 version: link:../config + '@prefabs.tech/fastify-error-handler': + specifier: 0.88.2 + version: link:../errorHandler '@prefabs.tech/fastify-graphql': specifier: 0.88.2 version: link:../graphql From 39c0db8af4fe7df6d9d552c82df249f1a8bf3ac6 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Mon, 11 Aug 2025 18:10:13 +0545 Subject: [PATCH 11/20] refactor: refactor error handler --- packages/errorHandler/package.json | 7 +++ packages/errorHandler/src/errorHandler.ts | 53 ++++++++++++++------ packages/errorHandler/src/index.ts | 13 ++--- packages/errorHandler/src/types.ts | 14 +++--- packages/errorHandler/src/utils/error.ts | 13 +++++ packages/errorHandler/src/utils/httpError.ts | 36 ------------- packages/errorHandler/vite.config.ts | 1 + 7 files changed, 67 insertions(+), 70 deletions(-) create mode 100644 packages/errorHandler/src/utils/error.ts delete mode 100644 packages/errorHandler/src/utils/httpError.ts diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index de601a2be..5a735b85b 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -29,12 +29,18 @@ "sort-package": "npx sort-package-json", "typecheck": "tsc --noEmit -p tsconfig.json --composite false" }, + "dependencies": { + "stack-trace":"1.0.0-pre2", + "statuses": "2.0.2" + }, "devDependencies": { "@fastify/sensible": "6.0.3", "@prefabs.tech/eslint-config": "0.2.0", "@prefabs.tech/fastify-config": "0.88.2", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", + "@types/stack-trace": "0.0.33", + "@types/statuses": "2.0.6", "@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/parser": "8.38.0", "@vitest/coverage-istanbul": "3.2.4", @@ -55,6 +61,7 @@ "vitest": "3.2.4" }, "peerDependencies": { + "@fastify/sensible": ">=6.0.3", "@prefabs.tech/fastify-config": "0.88.2", "fastify": ">=5.2.1", "fastify-plugin": ">=5.0.1" diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/errorHandler/src/errorHandler.ts index 66ae4d1ef..f9439902e 100644 --- a/packages/errorHandler/src/errorHandler.ts +++ b/packages/errorHandler/src/errorHandler.ts @@ -1,9 +1,14 @@ import { HttpError } from "@fastify/sensible"; import { FastifyReply, FastifyRequest } from "fastify"; +import { parse } from "stack-trace"; +import status from "statuses"; -import { parseStack } from "./utils/httpError"; +import { CustomError } from "./utils/error"; -import type { ErrorResponse, StackFrame } from "./types"; +import type { ErrorResponse } from "./types"; + +const getHttpStatusText = (statusCode: number): string => + status(statusCode) ?? "Internal Server Error"; export const errorHandler = ( error: Error, @@ -22,19 +27,22 @@ export const errorHandler = ( if (statusCode >= 500) { logger.error(error); - } else { + } else if (statusCode >= 400) { logger.info(error); + } else { + logger.error(error); } const response: ErrorResponse = { - code: error.code ?? "INTERNAL_ERROR", + code: error.code, + error: error.error || getHttpStatusText(statusCode), message: error.message, name: error.name, statusCode, }; if (isStackTraceEnabled && error.stack) { - response.stack = parseStack(error.stack); + response.stack = parse(error); } void reply.code(statusCode).send(response); @@ -42,20 +50,33 @@ export const errorHandler = ( return; } - // Unhandled error - logger.error(error); + let message = "Server error, please contact support"; + let code = "INTERNAL_SERVER_ERROR"; - const response = { - code: "INTERNAL_ERROR", - message: error.message, - name: error.name, - stack: [] as StackFrame[], - statusCode: 500, - }; + if (error instanceof CustomError) { + code = error.code || code; + message = "Server has an error that is not handled, please contact support"; + } if (isStackTraceEnabled && error.stack) { - response.stack = parseStack(error.stack); + const response: ErrorResponse = { + code: code, + message: error.message, + name: error.name, + statusCode: 500, + }; + + response.stack = parse(error); + + void reply.code(500).send(response); + + return; } - void reply.code(500).send(response); + // remove stack and message from error + delete error.stack; + error.message = message; + + // let fastify handle the error + throw error; }; diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts index 0c2e54701..0f8eda5fd 100644 --- a/packages/errorHandler/src/index.ts +++ b/packages/errorHandler/src/index.ts @@ -1,13 +1,6 @@ -import type { HttpErrors } from "@fastify/sensible"; /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ import type { ApiConfig } from "@prefabs.tech/fastify-config"; -declare module "fastify" { - interface FastifyInstance { - httpErrors: HttpErrors; - } -} - declare module "@prefabs.tech/fastify-config" { interface ApiConfig { errorHandler: { @@ -16,8 +9,8 @@ declare module "@prefabs.tech/fastify-config" { } } -export { default } from "./plugin"; +export { errorHandler } from "./errorHandler"; -export type { ErrorResponse } from "./types"; +export { CustomError } from "./utils/error"; -export type { HttpErrors } from "@fastify/sensible"; +export type { ErrorResponse } from "./types"; diff --git a/packages/errorHandler/src/types.ts b/packages/errorHandler/src/types.ts index 47fd0a209..bbc0afe72 100644 --- a/packages/errorHandler/src/types.ts +++ b/packages/errorHandler/src/types.ts @@ -1,16 +1,14 @@ -interface StackFrame { - columnNumber: number; - fileName: string; - functionName: string; - lineNumber: number; -} +import type { StackFrame } from "stack-trace"; type ErrorResponse = { - code: string; + error?: string; + code?: string; message: string; name: string; stack?: StackFrame[]; statusCode: number; }; -export type { ErrorResponse, StackFrame }; +export type { ErrorResponse }; + +export { type StackFrame } from "stack-trace"; diff --git a/packages/errorHandler/src/utils/error.ts b/packages/errorHandler/src/utils/error.ts new file mode 100644 index 000000000..e30e3c8c7 --- /dev/null +++ b/packages/errorHandler/src/utils/error.ts @@ -0,0 +1,13 @@ +export class CustomError extends Error { + public code?: string; + + constructor(message: string, code?: string) { + super(message); + + this.code = code; + this.name = this.constructor.name; // sets name to "CustomError" so that it works in logs + + // (error instanceof CustomError) and (error instanceof Error) both works because of this + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/packages/errorHandler/src/utils/httpError.ts b/packages/errorHandler/src/utils/httpError.ts deleted file mode 100644 index ea890e25d..000000000 --- a/packages/errorHandler/src/utils/httpError.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { StackFrame } from "../types"; - -export const parseStack = (stack: string): StackFrame[] => { - // split stack into lines and remove the first line (error message) - const stackLines = stack.split("\n").slice(1); - - return stackLines - .map((line) => { - // match the stack trace line pattern - const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); - - if (match) { - return { - columnNumber: Number.parseInt(match[4], 10), - fileName: match[2].trim(), - functionName: match[1].trim(), - lineNumber: Number.parseInt(match[3], 10), - }; - } - - // Handle cases where the stack trace format is different - const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/); - - if (simpleMatch) { - return { - columnNumber: Number.parseInt(simpleMatch[3], 10), - fileName: simpleMatch[1].trim(), - functionName: "anonymous", - lineNumber: Number.parseInt(simpleMatch[2], 10), - }; - } - - return; - }) - .filter((frame): frame is StackFrame => frame !== undefined); -}; diff --git a/packages/errorHandler/vite.config.ts b/packages/errorHandler/vite.config.ts index e41613280..f11e42b72 100644 --- a/packages/errorHandler/vite.config.ts +++ b/packages/errorHandler/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig(({ mode }) => { output: { exports: "named", globals: { + "@fastify/sensible": "fastifySensible", fastify: "Fastify", "fastify-plugin": "FastifyPlugin", }, From 373eb8e3e691d4e83dd81d15c46b5573faa3b176 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Mon, 11 Aug 2025 18:11:17 +0545 Subject: [PATCH 12/20] chore: update dependencies --- pnpm-lock.yaml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12bbf3430..9684149d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,13 @@ importers: version: 3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1) packages/errorHandler: + dependencies: + stack-trace: + specifier: 1.0.0-pre2 + version: 1.0.0-pre2 + statuses: + specifier: 2.0.2 + version: 2.0.2 devDependencies: '@fastify/sensible': specifier: 6.0.3 @@ -116,6 +123,12 @@ importers: '@types/node': specifier: 20.19.9 version: 20.19.9 + '@types/stack-trace': + specifier: 0.0.33 + version: 0.0.33 + '@types/statuses': + specifier: 2.0.6 + version: 2.0.6 '@typescript-eslint/eslint-plugin': specifier: 8.38.0 version: 8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) @@ -2150,6 +2163,12 @@ packages: '@types/serve-static@1.15.5': resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + '@types/stack-trace@0.0.33': + resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -5493,6 +5512,10 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stack-trace@1.0.0-pre2: + resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} + engines: {node: '>=16'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -5504,6 +5527,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -7999,6 +8026,10 @@ snapshots: '@types/mime': 3.0.4 '@types/node': 22.9.0 + '@types/stack-trace@0.0.33': {} + + '@types/statuses@2.0.6': {} + '@types/tough-cookie@4.0.5': optional: true @@ -11943,6 +11974,8 @@ snapshots: stable-hash@0.0.5: {} + stack-trace@1.0.0-pre2: {} + stackback@0.0.2: {} stacktrace-parser@0.1.10: @@ -11951,6 +11984,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + std-env@3.9.0: {} stop-iteration-iterator@1.1.0: From 56d91edd80d2f58279d2ecd7f87e15ebd5745a73 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Mon, 11 Aug 2025 18:16:48 +0545 Subject: [PATCH 13/20] refactor: add setErrorHandler config to toggle setting error supertokens error handler --- packages/user/src/supertokens/plugin.ts | 27 ++++++++++++-------- packages/user/src/supertokens/types/index.ts | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/user/src/supertokens/plugin.ts b/packages/user/src/supertokens/plugin.ts index 93dce4a4c..949d70fe9 100644 --- a/packages/user/src/supertokens/plugin.ts +++ b/packages/user/src/supertokens/plugin.ts @@ -10,7 +10,12 @@ import { verifySession } from "supertokens-node/recipe/session/framework/fastify import init from "./init"; -import type { FastifyError, FastifyInstance } from "fastify"; +import type { + FastifyError, + FastifyInstance, + FastifyReply, + FastifyRequest, +} from "fastify"; const plugin = async (fastify: FastifyInstance) => { const { config, log } = fastify; @@ -19,16 +24,16 @@ const plugin = async (fastify: FastifyInstance) => { init(fastify); - // Explicitly cast the errorHandler to the correct type - // [RL 2025-04-01] This should be fixed when supertokens-node is updated - fastify.setErrorHandler( - errorHandler() as unknown as ( - this: FastifyInstance, - error: FastifyError, - request: import("fastify").FastifyRequest, - reply: import("fastify").FastifyReply, - ) => void, - ); + if (config.user.supertokens.setErrorHandler !== false) { + fastify.setErrorHandler( + errorHandler() as unknown as ( + this: FastifyInstance, + error: FastifyError, + request: FastifyRequest, + reply: FastifyReply, + ) => void, + ); + } await fastify.register(cors, { origin: config.appOrigin, diff --git a/packages/user/src/supertokens/types/index.ts b/packages/user/src/supertokens/types/index.ts index 495e2aaee..b22ad8302 100644 --- a/packages/user/src/supertokens/types/index.ts +++ b/packages/user/src/supertokens/types/index.ts @@ -47,6 +47,7 @@ interface SupertokensConfig { resetPasswordPath?: string; emailVerificationPath?: string; sendUserAlreadyExistsWarning?: boolean; + setErrorHandler?: boolean; } export type { SupertokensConfig, SupertokensRecipes }; From f86b5cd068fe37aeaa03dff96e222a5975413f52 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 14:44:47 +0545 Subject: [PATCH 14/20] feat: add error handler plugin and add support for pre error handler --- packages/errorHandler/README.md | 2 +- packages/errorHandler/package.json | 7 ++----- packages/errorHandler/src/errorHandler.ts | 8 ++++---- packages/errorHandler/src/index.ts | 18 ++++++++++-------- packages/errorHandler/src/plugin.ts | 20 ++++++++++++++++++-- packages/errorHandler/src/types.ts | 15 ++++++++++++++- packages/errorHandler/vite.config.ts | 12 +++++++++--- pnpm-lock.yaml | 23 +++-------------------- 8 files changed, 61 insertions(+), 44 deletions(-) diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md index e49d187af..fd989ed02 100644 --- a/packages/errorHandler/README.md +++ b/packages/errorHandler/README.md @@ -40,7 +40,7 @@ const start = async () => { }); // Register fastify-error-handler plugin - await fastify.register(errorHandlerPlugin); + await fastify.register(errorHandlerPlugin, {}); await fastify.listen({ port: config.port, diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index 5a735b85b..c3bd83f96 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -30,17 +30,15 @@ "typecheck": "tsc --noEmit -p tsconfig.json --composite false" }, "dependencies": { - "stack-trace":"1.0.0-pre2", - "statuses": "2.0.2" + "@fastify/sensible": "6.0.3", + "stack-trace":"1.0.0-pre2" }, "devDependencies": { - "@fastify/sensible": "6.0.3", "@prefabs.tech/eslint-config": "0.2.0", "@prefabs.tech/fastify-config": "0.88.2", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", "@types/stack-trace": "0.0.33", - "@types/statuses": "2.0.6", "@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/parser": "8.38.0", "@vitest/coverage-istanbul": "3.2.4", @@ -61,7 +59,6 @@ "vitest": "3.2.4" }, "peerDependencies": { - "@fastify/sensible": ">=6.0.3", "@prefabs.tech/fastify-config": "0.88.2", "fastify": ">=5.2.1", "fastify-plugin": ">=5.0.1" diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/errorHandler/src/errorHandler.ts index f9439902e..6f8047df0 100644 --- a/packages/errorHandler/src/errorHandler.ts +++ b/packages/errorHandler/src/errorHandler.ts @@ -1,14 +1,15 @@ +import { STATUS_CODES } from "node:http"; + import { HttpError } from "@fastify/sensible"; import { FastifyReply, FastifyRequest } from "fastify"; import { parse } from "stack-trace"; -import status from "statuses"; import { CustomError } from "./utils/error"; import type { ErrorResponse } from "./types"; const getHttpStatusText = (statusCode: number): string => - status(statusCode) ?? "Internal Server Error"; + STATUS_CODES[statusCode] ?? "Internal Server Error"; export const errorHandler = ( error: Error, @@ -17,8 +18,7 @@ export const errorHandler = ( ) => { const { log: logger } = request; - const isStackTraceEnabled = - request.server.config.errorHandler?.stackTrace || false; + const isStackTraceEnabled = request.server.stackTrace || false; const isHttpError = error instanceof HttpError; diff --git a/packages/errorHandler/src/index.ts b/packages/errorHandler/src/index.ts index 0f8eda5fd..0d5f8f8bf 100644 --- a/packages/errorHandler/src/index.ts +++ b/packages/errorHandler/src/index.ts @@ -1,16 +1,18 @@ -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -import type { ApiConfig } from "@prefabs.tech/fastify-config"; +import type { HttpErrors } from "@fastify/sensible"; -declare module "@prefabs.tech/fastify-config" { - interface ApiConfig { - errorHandler: { - stackTrace?: boolean; - }; +declare module "fastify" { + interface FastifyInstance { + httpErrors: HttpErrors; + stackTrace: boolean; } } +export { default } from "./plugin"; + export { errorHandler } from "./errorHandler"; export { CustomError } from "./utils/error"; -export type { ErrorResponse } from "./types"; +export type { HttpErrors } from "@fastify/sensible"; + +export type * from "./types"; diff --git a/packages/errorHandler/src/plugin.ts b/packages/errorHandler/src/plugin.ts index e71a6d55e..c790fec7e 100644 --- a/packages/errorHandler/src/plugin.ts +++ b/packages/errorHandler/src/plugin.ts @@ -3,14 +3,30 @@ import FastifyPlugin from "fastify-plugin"; import { errorHandler } from "./errorHandler"; +import type { ErrorHandlerOptions } from "./types"; import type { FastifyInstance } from "fastify"; -const plugin = async (fastify: FastifyInstance) => { +const plugin = async ( + fastify: FastifyInstance, + options: ErrorHandlerOptions, +) => { fastify.log.info("Registering fastify-error-handler plugin"); + fastify.decorate("stackTrace", options.stackTrace || false); + await fastify.register(fastifySensible); - await fastify.setErrorHandler(errorHandler); + fastify.setErrorHandler(async (error, request, reply) => { + if (options.preErrorHandler) { + await options.preErrorHandler(error, request, reply); + + if (reply.sent) { + return; + } + } + + return errorHandler(error, request, reply); + }); }; export default FastifyPlugin(plugin); diff --git a/packages/errorHandler/src/types.ts b/packages/errorHandler/src/types.ts index bbc0afe72..930a7ca6c 100644 --- a/packages/errorHandler/src/types.ts +++ b/packages/errorHandler/src/types.ts @@ -1,5 +1,18 @@ +import { FastifyError, FastifyRequest, FastifyReply } from "fastify"; + import type { StackFrame } from "stack-trace"; +type ErrorHandler = ( + error: FastifyError, + request: FastifyRequest, + reply: FastifyReply, +) => void | Promise; + +interface ErrorHandlerOptions { + preErrorHandler?: ErrorHandler; + stackTrace?: boolean; +} + type ErrorResponse = { error?: string; code?: string; @@ -9,6 +22,6 @@ type ErrorResponse = { statusCode: number; }; -export type { ErrorResponse }; +export type { ErrorHandler, ErrorHandlerOptions, ErrorResponse }; export { type StackFrame } from "stack-trace"; diff --git a/packages/errorHandler/vite.config.ts b/packages/errorHandler/vite.config.ts index f11e42b72..813ef1e4c 100644 --- a/packages/errorHandler/vite.config.ts +++ b/packages/errorHandler/vite.config.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url"; import { defineConfig, loadEnv } from "vite"; -import { peerDependencies } from "./package.json"; +import { dependencies, peerDependencies } from "./package.json"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { @@ -17,13 +17,19 @@ export default defineConfig(({ mode }) => { name: "PrefabsTechFastifyErrorHandler", }, rollupOptions: { - external: Object.keys(peerDependencies), + external: [ + ...Object.keys(dependencies), + ...Object.keys(peerDependencies), + "node:http", + ], output: { exports: "named", globals: { - "@fastify/sensible": "fastifySensible", + "@fastify/sensible": "FastifySensible", fastify: "Fastify", "fastify-plugin": "FastifyPlugin", + "node:http": "NodeHttp", + "stack-trace": "StackTrace", }, }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9684149d2..012c738ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,16 +101,13 @@ importers: packages/errorHandler: dependencies: + '@fastify/sensible': + specifier: 6.0.3 + version: 6.0.3 stack-trace: specifier: 1.0.0-pre2 version: 1.0.0-pre2 - statuses: - specifier: 2.0.2 - version: 2.0.2 devDependencies: - '@fastify/sensible': - specifier: 6.0.3 - version: 6.0.3 '@prefabs.tech/eslint-config': specifier: 0.2.0 version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) @@ -126,9 +123,6 @@ importers: '@types/stack-trace': specifier: 0.0.33 version: 0.0.33 - '@types/statuses': - specifier: 2.0.6 - version: 2.0.6 '@typescript-eslint/eslint-plugin': specifier: 8.38.0 version: 8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) @@ -2166,9 +2160,6 @@ packages: '@types/stack-trace@0.0.33': resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==} - '@types/statuses@2.0.6': - resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -5527,10 +5518,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -8028,8 +8015,6 @@ snapshots: '@types/stack-trace@0.0.33': {} - '@types/statuses@2.0.6': {} - '@types/tough-cookie@4.0.5': optional: true @@ -11984,8 +11969,6 @@ snapshots: statuses@2.0.1: {} - statuses@2.0.2: {} - std-env@3.9.0: {} stop-iteration-iterator@1.1.0: From d5ad4bf3af827524e3aa5c81173b714fdf8c1b7b Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 16:35:12 +0545 Subject: [PATCH 15/20] fix: pass error to error handler if pre error handler throws an error --- packages/errorHandler/src/plugin.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/errorHandler/src/plugin.ts b/packages/errorHandler/src/plugin.ts index c790fec7e..f186210ae 100644 --- a/packages/errorHandler/src/plugin.ts +++ b/packages/errorHandler/src/plugin.ts @@ -18,7 +18,11 @@ const plugin = async ( fastify.setErrorHandler(async (error, request, reply) => { if (options.preErrorHandler) { - await options.preErrorHandler(error, request, reply); + try { + await options.preErrorHandler(error, request, reply); + } catch { + // If preErrorHandler throws an error, we can ignore it and continue + } if (reply.sent) { return; From a5f24ea4ab4c6071f5d768ebba80b2ec4ddd8068 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 17:32:55 +0545 Subject: [PATCH 16/20] chore: remove unused dependencies --- packages/user/package.json | 2 -- pnpm-lock.yaml | 3 --- 2 files changed, 5 deletions(-) diff --git a/packages/user/package.json b/packages/user/package.json index 2fbcc8e58..15d0411d8 100644 --- a/packages/user/package.json +++ b/packages/user/package.json @@ -37,7 +37,6 @@ "@fastify/formbody": "8.0.2", "@prefabs.tech/eslint-config": "0.2.0", "@prefabs.tech/fastify-config": "0.88.2", - "@prefabs.tech/fastify-error-handler": "0.88.2", "@prefabs.tech/fastify-graphql": "0.88.2", "@prefabs.tech/fastify-mailer": "0.88.2", "@prefabs.tech/fastify-s3": "0.88.2", @@ -75,7 +74,6 @@ "@fastify/cors": ">=11.0.1", "@fastify/formbody": ">=8.0.2", "@prefabs.tech/fastify-config": "0.88.2", - "@prefabs.tech/fastify-error-handler": "0.88.2", "@prefabs.tech/fastify-graphql": "0.88.2", "@prefabs.tech/fastify-mailer": "0.88.2", "@prefabs.tech/fastify-s3": "0.88.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 012c738ef..a7437f508 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -769,9 +769,6 @@ importers: '@prefabs.tech/fastify-config': specifier: 0.88.2 version: link:../config - '@prefabs.tech/fastify-error-handler': - specifier: 0.88.2 - version: link:../errorHandler '@prefabs.tech/fastify-graphql': specifier: 0.88.2 version: link:../graphql From 402288be5b84b3ec03a7507279c51a7306ba862b Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 17:58:26 +0545 Subject: [PATCH 17/20] docs: update readme file --- packages/errorHandler/README.md | 34 +++++++++++++++++++++++++----- packages/errorHandler/package.json | 2 -- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/errorHandler/README.md b/packages/errorHandler/README.md index fd989ed02..2991c6cfa 100644 --- a/packages/errorHandler/README.md +++ b/packages/errorHandler/README.md @@ -27,17 +27,15 @@ pnpm add --filter "@scope/project @prefabs.tech/fastify-error-handler Register @prefabs.tech/fastify-error-handler package with your Fastify instance: +Note: Register the errorHandler plugin as early as possible (Before all your routes and plugin registration). + ```typescript import errorHandlerPlugin from "@prefabs.tech/fastify-error-handler"; import Fastify from "fastify"; -import config from "./config"; - const start = async () => { // Create fastify instance - const fastify = Fastify({ - logger: config.logger, - }); + const fastify = Fastify(); // Register fastify-error-handler plugin await fastify.register(errorHandlerPlugin, {}); @@ -50,3 +48,29 @@ const start = async () => { start(); ``` +### Options + +#### stackTrace + +When enabled, the error handler will include the error’s stack trace in the HTTP response body. + +By default, it is set to false. + +```ts +stackTrace?: boolean; // Default: false +``` + +#### preErrorHandler + +preErrorHandler is an optional error handler that runs before the default error handler logic. +It allows you to intercept specific errors, handle them yourself, and prevent the default handler from running. + +This is especially useful when you need to integrate with other libraries that have their own error formats — for example, handling SuperTokens errors before your API’s standard error response. + +```ts +preErrorHandler?: ( + error: FastifyError, + request: FastifyRequest, + reply: FastifyReply, +) => void | Promise; +``` diff --git a/packages/errorHandler/package.json b/packages/errorHandler/package.json index c3bd83f96..b8f2251f9 100644 --- a/packages/errorHandler/package.json +++ b/packages/errorHandler/package.json @@ -35,7 +35,6 @@ }, "devDependencies": { "@prefabs.tech/eslint-config": "0.2.0", - "@prefabs.tech/fastify-config": "0.88.2", "@prefabs.tech/tsconfig": "0.2.0", "@types/node": "20.19.9", "@types/stack-trace": "0.0.33", @@ -59,7 +58,6 @@ "vitest": "3.2.4" }, "peerDependencies": { - "@prefabs.tech/fastify-config": "0.88.2", "fastify": ">=5.2.1", "fastify-plugin": ">=5.0.1" }, From 49d5430b4fc62b67dc1817540065a87c80ac037d Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 18:00:11 +0545 Subject: [PATCH 18/20] chore: update dependencies --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7437f508..848374369 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,9 +111,6 @@ importers: '@prefabs.tech/eslint-config': specifier: 0.2.0 version: 0.2.0(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(@typescript-eslint/parser@8.38.0(eslint@8.57.1)(typescript@5.8.3))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-n@14.0.0(eslint@8.57.1))(eslint-plugin-prettier@5.5.3(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2))(eslint-plugin-promise@7.2.1(eslint@8.57.1))(eslint-plugin-unicorn@56.0.1(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2)(typescript@5.8.3) - '@prefabs.tech/fastify-config': - specifier: 0.88.2 - version: link:../config '@prefabs.tech/tsconfig': specifier: 0.2.0 version: 0.2.0(@types/node@20.19.9) From 1c16a04c0f81631a365e65e66945613015deda33 Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 18:07:37 +0545 Subject: [PATCH 19/20] chore: rename package directory --- packages/{errorHandler => error-handler}/.eslintignore | 0 packages/{errorHandler => error-handler}/.eslintrc.cjs | 0 packages/{errorHandler => error-handler}/.gitignore | 0 packages/{errorHandler => error-handler}/README.md | 0 packages/{errorHandler => error-handler}/package.json | 2 +- packages/{errorHandler => error-handler}/src/errorHandler.ts | 0 packages/{errorHandler => error-handler}/src/index.ts | 0 packages/{errorHandler => error-handler}/src/plugin.ts | 0 packages/{errorHandler => error-handler}/src/types.ts | 0 packages/{errorHandler => error-handler}/src/utils/error.ts | 0 packages/{errorHandler => error-handler}/tsconfig.json | 0 packages/{errorHandler => error-handler}/vite.config.ts | 0 12 files changed, 1 insertion(+), 1 deletion(-) rename packages/{errorHandler => error-handler}/.eslintignore (100%) rename packages/{errorHandler => error-handler}/.eslintrc.cjs (100%) rename packages/{errorHandler => error-handler}/.gitignore (100%) rename packages/{errorHandler => error-handler}/README.md (100%) rename packages/{errorHandler => error-handler}/package.json (97%) rename packages/{errorHandler => error-handler}/src/errorHandler.ts (100%) rename packages/{errorHandler => error-handler}/src/index.ts (100%) rename packages/{errorHandler => error-handler}/src/plugin.ts (100%) rename packages/{errorHandler => error-handler}/src/types.ts (100%) rename packages/{errorHandler => error-handler}/src/utils/error.ts (100%) rename packages/{errorHandler => error-handler}/tsconfig.json (100%) rename packages/{errorHandler => error-handler}/vite.config.ts (100%) diff --git a/packages/errorHandler/.eslintignore b/packages/error-handler/.eslintignore similarity index 100% rename from packages/errorHandler/.eslintignore rename to packages/error-handler/.eslintignore diff --git a/packages/errorHandler/.eslintrc.cjs b/packages/error-handler/.eslintrc.cjs similarity index 100% rename from packages/errorHandler/.eslintrc.cjs rename to packages/error-handler/.eslintrc.cjs diff --git a/packages/errorHandler/.gitignore b/packages/error-handler/.gitignore similarity index 100% rename from packages/errorHandler/.gitignore rename to packages/error-handler/.gitignore diff --git a/packages/errorHandler/README.md b/packages/error-handler/README.md similarity index 100% rename from packages/errorHandler/README.md rename to packages/error-handler/README.md diff --git a/packages/errorHandler/package.json b/packages/error-handler/package.json similarity index 97% rename from packages/errorHandler/package.json rename to packages/error-handler/package.json index b8f2251f9..3581d4978 100644 --- a/packages/errorHandler/package.json +++ b/packages/error-handler/package.json @@ -6,7 +6,7 @@ "repository": { "type": "git", "url": "git+https://github.com/prefabs-tech/fastify.git", - "directory": "packages/errorHandler" + "directory": "packages/error-handler" }, "license": "MIT", "type": "module", diff --git a/packages/errorHandler/src/errorHandler.ts b/packages/error-handler/src/errorHandler.ts similarity index 100% rename from packages/errorHandler/src/errorHandler.ts rename to packages/error-handler/src/errorHandler.ts diff --git a/packages/errorHandler/src/index.ts b/packages/error-handler/src/index.ts similarity index 100% rename from packages/errorHandler/src/index.ts rename to packages/error-handler/src/index.ts diff --git a/packages/errorHandler/src/plugin.ts b/packages/error-handler/src/plugin.ts similarity index 100% rename from packages/errorHandler/src/plugin.ts rename to packages/error-handler/src/plugin.ts diff --git a/packages/errorHandler/src/types.ts b/packages/error-handler/src/types.ts similarity index 100% rename from packages/errorHandler/src/types.ts rename to packages/error-handler/src/types.ts diff --git a/packages/errorHandler/src/utils/error.ts b/packages/error-handler/src/utils/error.ts similarity index 100% rename from packages/errorHandler/src/utils/error.ts rename to packages/error-handler/src/utils/error.ts diff --git a/packages/errorHandler/tsconfig.json b/packages/error-handler/tsconfig.json similarity index 100% rename from packages/errorHandler/tsconfig.json rename to packages/error-handler/tsconfig.json diff --git a/packages/errorHandler/vite.config.ts b/packages/error-handler/vite.config.ts similarity index 100% rename from packages/errorHandler/vite.config.ts rename to packages/error-handler/vite.config.ts From e2a8cf2e53febbf6c5bba675f262ea61e7e1b10b Mon Sep 17 00:00:00 2001 From: Ramesh Date: Tue, 12 Aug 2025 18:11:24 +0545 Subject: [PATCH 20/20] chore: update dependencies --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 848374369..36995490d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,7 +99,7 @@ importers: specifier: 3.2.4 version: 3.2.4(@types/node@20.19.9)(jiti@2.4.2)(yaml@2.7.1) - packages/errorHandler: + packages/error-handler: dependencies: '@fastify/sensible': specifier: 6.0.3