@bugwatch/fastify 0.1.0 → 0.2.0

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/dist/index.js CHANGED
@@ -167,14 +167,14 @@ var bugwatchPluginImpl = async (fastify, options) => {
167
167
  ...options
168
168
  };
169
169
  const requestStartTimes = /* @__PURE__ */ new WeakMap();
170
- const requestContexts = /* @__PURE__ */ new WeakMap();
171
170
  fastify.decorateRequest("bugwatch", null);
172
- fastify.addHook("onRequest", async (request) => {
171
+ fastify.addHook("onRequest", (request, _reply, done) => {
173
172
  const client = (0, import_core.getClient)();
174
- if (!client) return;
173
+ if (!client) {
174
+ return done();
175
+ }
175
176
  requestStartTimes.set(request, Date.now());
176
177
  const scopedContext = (0, import_core.createScopedContext)();
177
- requestContexts.set(request, scopedContext);
178
178
  request.bugwatch = createBugwatchDecorator(request, opts);
179
179
  if (opts.extractUser) {
180
180
  const user = opts.extractUser(request);
@@ -182,17 +182,20 @@ var bugwatchPluginImpl = async (fastify, options) => {
182
182
  scopedContext.user = user;
183
183
  }
184
184
  }
185
- if (opts.addBreadcrumbs) {
186
- (0, import_core.addBreadcrumb)({
187
- category: "http",
188
- message: `${request.method} ${request.url}`,
189
- level: "info",
190
- data: {
191
- method: request.method,
192
- url: request.url
193
- }
194
- });
195
- }
185
+ (0, import_core.runWithContext)(scopedContext, () => {
186
+ if (opts.addBreadcrumbs) {
187
+ (0, import_core.addBreadcrumb)({
188
+ category: "http",
189
+ message: `${request.method} ${request.url}`,
190
+ level: "info",
191
+ data: {
192
+ method: request.method,
193
+ url: request.url
194
+ }
195
+ });
196
+ }
197
+ done();
198
+ });
196
199
  });
197
200
  fastify.addHook("onResponse", async (request, reply) => {
198
201
  if (!opts.addBreadcrumbs) return;
@@ -212,7 +215,7 @@ var bugwatchPluginImpl = async (fastify, options) => {
212
215
  }
213
216
  });
214
217
  });
215
- fastify.addHook("onError", async (request, reply, error) => {
218
+ fastify.addHook("onError", async (request, _reply, error) => {
216
219
  if (!opts.captureErrors) return;
217
220
  const client = (0, import_core.getClient)();
218
221
  if (!client) return;
@@ -221,6 +224,9 @@ var bugwatchPluginImpl = async (fastify, options) => {
221
224
  await (0, import_core.flush)();
222
225
  }
223
226
  });
227
+ fastify.addHook("onClose", async () => {
228
+ await (0, import_core.close)();
229
+ });
224
230
  };
225
231
  var bugwatchPlugin = (0, import_fastify_plugin.default)(bugwatchPluginImpl, {
226
232
  fastify: "4.x || 5.x",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/plugin.ts"],"sourcesContent":["/**\n * @bugwatch/fastify - Fastify integration for Bugwatch\n *\n * This package provides a plugin for automatically capturing errors\n * and request context in Fastify applications.\n *\n * @example One-liner setup (recommended)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Single call handles everything (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n *\n * @example Manual setup (for more control)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n * await fastify.register(bugwatchPlugin);\n *\n * // request.bugwatch is available for manual capture\n * fastify.get(\"/risky\", async (request) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\n\n// Export one-liner setup\nexport { setup } from \"./setup\";\nexport type { BugwatchFastifySetupOptions } from \"./setup\";\n\n// Export plugin\nexport { bugwatchPlugin } from \"./plugin\";\n\n// Export types\nexport type {\n BugwatchFastifyOptions,\n BugwatchDecorator,\n} from \"./types\";\n\n// Re-export core functions for convenience\nexport {\n init,\n getClient,\n captureException,\n captureMessage,\n addBreadcrumb,\n setUser,\n setTag,\n setExtra,\n flush,\n close,\n} from \"@bugwatch/core\";\n","/**\n * One-liner setup for Bugwatch Fastify integration.\n *\n * This module provides a simplified setup function that handles all\n * initialization and plugin registration in a single call.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport { init, getClient, type BugwatchOptions } from \"@bugwatch/core\";\nimport { bugwatchPlugin } from \"./plugin\";\nimport type { BugwatchFastifyOptions } from \"./types\";\n\n/**\n * Combined options for Fastify setup.\n * Includes both core SDK options and Fastify-specific plugin options.\n */\nexport interface BugwatchFastifySetupOptions\n extends BugwatchFastifyOptions,\n Partial<BugwatchOptions> {}\n\n/**\n * Set up Bugwatch for a Fastify application with a single call.\n *\n * This function:\n * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)\n * 2. Registers the Bugwatch plugin with the Fastify instance\n *\n * @param fastify - The Fastify instance\n * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)\n * @returns Promise that resolves when the plugin is registered\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Minimal setup (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * environment: \"production\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport async function setup(\n fastify: FastifyInstance,\n options?: BugwatchFastifySetupOptions\n): Promise<void> {\n // Initialize core SDK if not already initialized\n if (!getClient()) {\n // Extract core options\n const coreOptions: Partial<BugwatchOptions> = {};\n if (options?.apiKey) coreOptions.apiKey = options.apiKey;\n if (options?.endpoint) coreOptions.endpoint = options.endpoint;\n if (options?.environment) coreOptions.environment = options.environment;\n if (options?.release) coreOptions.release = options.release;\n if (options?.debug !== undefined) coreOptions.debug = options.debug;\n if (options?.sampleRate !== undefined) coreOptions.sampleRate = options.sampleRate;\n if (options?.maxBreadcrumbs !== undefined) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;\n if (options?.tags) coreOptions.tags = options.tags;\n if (options?.user) coreOptions.user = options.user;\n if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;\n if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;\n\n init(coreOptions);\n }\n\n // Extract plugin-specific options\n const pluginOptions: BugwatchFastifyOptions = {};\n if (options?.extractUser) pluginOptions.extractUser = options.extractUser;\n if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;\n if (options?.filterBody) pluginOptions.filterBody = options.filterBody;\n if (options?.includeBody !== undefined) pluginOptions.includeBody = options.includeBody;\n if (options?.addBreadcrumbs !== undefined) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;\n if (options?.captureErrors !== undefined) pluginOptions.captureErrors = options.captureErrors;\n if (options?.flushOnError !== undefined) pluginOptions.flushOnError = options.flushOnError;\n\n // Register the plugin\n await fastify.register(bugwatchPlugin, pluginOptions);\n}\n","import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport {\n getClient,\n captureException,\n captureMessage as coreCaptureMessage,\n addBreadcrumb,\n setUser,\n flush,\n runWithContextAsync,\n createScopedContext,\n type RequestContext,\n type BugwatchClient,\n type ScopedContext,\n} from \"@bugwatch/core\";\nimport type { BugwatchFastifyOptions, BugwatchDecorator } from \"./types\";\n\n/**\n * Headers that should be filtered out by default for security.\n */\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n \"proxy-authorization\",\n]);\n\n/**\n * Body fields that should be filtered out by default for security.\n */\nconst SENSITIVE_BODY_FIELDS = new Set([\n \"password\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apiKey\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"social_security\",\n]);\n\n/**\n * Default header filter function.\n */\nfunction defaultHeaderFilter(name: string): boolean {\n return !SENSITIVE_HEADERS.has(name.toLowerCase());\n}\n\n/**\n * Default body field filter function.\n */\nfunction defaultBodyFilter(key: string): boolean {\n return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());\n}\n\n/**\n * Filter an object's keys based on a filter function.\n */\nfunction filterObject<T extends Record<string, unknown>>(\n obj: T,\n filter: (key: string, value: unknown) => boolean\n): Partial<T> {\n const result: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (filter(key, value)) {\n (result as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Extract request context from a Fastify request.\n */\nfunction extractRequestContext(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): RequestContext {\n const headerFilter = options.filterHeaders || defaultHeaderFilter;\n const bodyFilter = options.filterBody || defaultBodyFilter;\n\n // Filter headers\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n if (typeof value === \"string\" && headerFilter(name, value)) {\n headers[name] = value;\n } else if (Array.isArray(value)) {\n const filtered = value.filter((v) => headerFilter(name, v));\n if (filtered.length > 0) {\n headers[name] = filtered.join(\", \");\n }\n }\n }\n\n const context: RequestContext = {\n url: request.url,\n method: request.method,\n headers,\n query_string: request.url.includes(\"?\") ? request.url.split(\"?\")[1] : undefined,\n };\n\n // Include body if requested and available\n if (options.includeBody && request.body && typeof request.body === \"object\") {\n context.data = filterObject(request.body as Record<string, unknown>, bodyFilter);\n }\n\n return context;\n}\n\n/**\n * Extract client IP from request.\n */\nfunction extractClientIp(request: FastifyRequest): string | undefined {\n // Check common proxy headers\n const forwarded = request.headers[\"x-forwarded-for\"];\n if (typeof forwarded === \"string\") {\n const firstIp = forwarded.split(\",\")[0]?.trim();\n if (firstIp) return firstIp;\n }\n\n const realIp = request.headers[\"x-real-ip\"];\n if (typeof realIp === \"string\") return realIp;\n\n const cfIp = request.headers[\"cf-connecting-ip\"];\n if (typeof cfIp === \"string\") return cfIp;\n\n // Fall back to connection address\n return request.ip;\n}\n\n/**\n * Create Bugwatch decorator for a request.\n */\nfunction createBugwatchDecorator(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): BugwatchDecorator {\n let eventId: string | undefined;\n const client = getClient();\n\n return {\n captureError(error: Error): string {\n if (!client) return \"\";\n\n const requestContext = extractRequestContext(request, options);\n const clientIp = extractClientIp(request);\n\n eventId = captureException(error, {\n request: requestContext,\n extra: {\n request: requestContext,\n ...(clientIp && { client_ip: clientIp }),\n },\n tags: {\n \"http.method\": request.method,\n \"http.url\": request.url,\n },\n });\n\n return eventId;\n },\n\n captureMessage(message: string, level: \"debug\" | \"info\" | \"warning\" | \"error\" = \"info\"): string {\n if (!client) return \"\";\n\n eventId = coreCaptureMessage(message, level);\n return eventId;\n },\n\n getEventId(): string | undefined {\n return eventId;\n },\n\n get client(): BugwatchClient | null {\n return client;\n },\n };\n}\n\n/**\n * Bugwatch plugin implementation.\n */\nconst bugwatchPluginImpl: FastifyPluginAsync<BugwatchFastifyOptions> = async (\n fastify,\n options\n) => {\n const opts: BugwatchFastifyOptions = {\n addBreadcrumbs: true,\n captureErrors: true,\n includeBody: false,\n flushOnError: false,\n ...options,\n };\n\n // Store request start times and scoped contexts\n const requestStartTimes = new WeakMap<FastifyRequest, number>();\n const requestContexts = new WeakMap<FastifyRequest, ScopedContext>();\n\n // Decorate requests with bugwatch\n fastify.decorateRequest(\"bugwatch\", null);\n\n // onRequest hook - set up context\n fastify.addHook(\"onRequest\", async (request) => {\n const client = getClient();\n if (!client) return;\n\n // Store start time\n requestStartTimes.set(request, Date.now());\n\n // Create a request-scoped context for isolation\n const scopedContext: ScopedContext = createScopedContext();\n requestContexts.set(request, scopedContext);\n\n // Create and attach decorator\n request.bugwatch = createBugwatchDecorator(request, opts);\n\n // Extract and set user context in the scoped context (not globally!)\n if (opts.extractUser) {\n const user = opts.extractUser(request);\n if (user) {\n scopedContext.user = user;\n }\n }\n\n // Add request breadcrumb to scoped context\n if (opts.addBreadcrumbs) {\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url}`,\n level: \"info\",\n data: {\n method: request.method,\n url: request.url,\n },\n });\n }\n });\n\n // onResponse hook - add completion breadcrumb\n fastify.addHook(\"onResponse\", async (request, reply) => {\n if (!opts.addBreadcrumbs) return;\n\n const client = getClient();\n if (!client) return;\n\n const startTime = requestStartTimes.get(request);\n const duration = startTime ? Date.now() - startTime : undefined;\n\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url} -> ${reply.statusCode}`,\n level: reply.statusCode >= 500 ? \"error\" : reply.statusCode >= 400 ? \"warning\" : \"info\",\n data: {\n method: request.method,\n url: request.url,\n status_code: reply.statusCode,\n ...(duration !== undefined && { duration_ms: duration }),\n },\n });\n });\n\n // onError hook - capture errors\n fastify.addHook(\"onError\", async (request, reply, error) => {\n if (!opts.captureErrors) return;\n\n const client = getClient();\n if (!client) return;\n\n // Capture the error\n request.bugwatch?.captureError(error);\n\n // Flush if requested\n if (opts.flushOnError) {\n await flush();\n }\n });\n};\n\n/**\n * Bugwatch Fastify plugin.\n *\n * This plugin automatically:\n * - Captures request context for errors\n * - Adds HTTP request breadcrumbs\n * - Reports unhandled errors to Bugwatch\n * - Provides `request.bugwatch` decorator for manual error capture\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * // Initialize Bugwatch\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n *\n * // Register the plugin\n * await fastify.register(bugwatchPlugin, {\n * extractUser: (request) => {\n * // Extract user from your auth system\n * return request.user ? { id: request.user.id } : null;\n * },\n * });\n *\n * // Your routes\n * fastify.get(\"/\", async (request, reply) => {\n * return { hello: \"world\" };\n * });\n *\n * // Manual error capture\n * fastify.get(\"/risky\", async (request, reply) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport const bugwatchPlugin = fp(bugwatchPluginImpl, {\n fastify: \"4.x || 5.x\",\n name: \"@bugwatch/fastify\",\n});\n"],"mappings":"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","names":["import_core","coreCaptureMessage","fp","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/plugin.ts"],"sourcesContent":["/**\n * @bugwatch/fastify - Fastify integration for Bugwatch\n *\n * This package provides a plugin for automatically capturing errors\n * and request context in Fastify applications.\n *\n * @example One-liner setup (recommended)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Single call handles everything (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n *\n * @example Manual setup (for more control)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n * await fastify.register(bugwatchPlugin);\n *\n * // request.bugwatch is available for manual capture\n * fastify.get(\"/risky\", async (request) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\n\n// Export one-liner setup\nexport { setup } from \"./setup\";\nexport type { BugwatchFastifySetupOptions } from \"./setup\";\n\n// Export plugin\nexport { bugwatchPlugin } from \"./plugin\";\n\n// Export types\nexport type {\n BugwatchFastifyOptions,\n BugwatchDecorator,\n} from \"./types\";\n\n// Re-export core functions for convenience\nexport {\n init,\n getClient,\n captureException,\n captureMessage,\n addBreadcrumb,\n setUser,\n setTag,\n setExtra,\n flush,\n close,\n} from \"@bugwatch/core\";\n","/**\n * One-liner setup for Bugwatch Fastify integration.\n *\n * This module provides a simplified setup function that handles all\n * initialization and plugin registration in a single call.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport { init, getClient, type BugwatchOptions } from \"@bugwatch/core\";\nimport { bugwatchPlugin } from \"./plugin\";\nimport type { BugwatchFastifyOptions } from \"./types\";\n\n/**\n * Combined options for Fastify setup.\n * Includes both core SDK options and Fastify-specific plugin options.\n */\nexport interface BugwatchFastifySetupOptions\n extends BugwatchFastifyOptions,\n Partial<BugwatchOptions> {}\n\n/**\n * Set up Bugwatch for a Fastify application with a single call.\n *\n * This function:\n * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)\n * 2. Registers the Bugwatch plugin with the Fastify instance\n *\n * @param fastify - The Fastify instance\n * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)\n * @returns Promise that resolves when the plugin is registered\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Minimal setup (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * environment: \"production\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport async function setup(\n fastify: FastifyInstance,\n options?: BugwatchFastifySetupOptions\n): Promise<void> {\n // Initialize core SDK if not already initialized\n if (!getClient()) {\n // Extract core options\n const coreOptions: Partial<BugwatchOptions> = {};\n if (options?.apiKey) coreOptions.apiKey = options.apiKey;\n if (options?.endpoint) coreOptions.endpoint = options.endpoint;\n if (options?.environment) coreOptions.environment = options.environment;\n if (options?.release) coreOptions.release = options.release;\n if (options?.debug !== undefined) coreOptions.debug = options.debug;\n if (options?.sampleRate !== undefined) coreOptions.sampleRate = options.sampleRate;\n if (options?.maxBreadcrumbs !== undefined) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;\n if (options?.tags) coreOptions.tags = options.tags;\n if (options?.user) coreOptions.user = options.user;\n if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;\n if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;\n\n init(coreOptions);\n }\n\n // Extract plugin-specific options\n const pluginOptions: BugwatchFastifyOptions = {};\n if (options?.extractUser) pluginOptions.extractUser = options.extractUser;\n if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;\n if (options?.filterBody) pluginOptions.filterBody = options.filterBody;\n if (options?.includeBody !== undefined) pluginOptions.includeBody = options.includeBody;\n if (options?.addBreadcrumbs !== undefined) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;\n if (options?.captureErrors !== undefined) pluginOptions.captureErrors = options.captureErrors;\n if (options?.flushOnError !== undefined) pluginOptions.flushOnError = options.flushOnError;\n\n // Register the plugin\n await fastify.register(bugwatchPlugin, pluginOptions);\n}\n","import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport {\n getClient,\n captureException,\n captureMessage as coreCaptureMessage,\n addBreadcrumb,\n setUser,\n flush,\n close,\n runWithContext,\n createScopedContext,\n type RequestContext,\n type BugwatchClient,\n type ScopedContext,\n} from \"@bugwatch/core\";\nimport type { BugwatchFastifyOptions, BugwatchDecorator } from \"./types\";\n\n/**\n * Headers that should be filtered out by default for security.\n */\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n \"proxy-authorization\",\n]);\n\n/**\n * Body fields that should be filtered out by default for security.\n */\nconst SENSITIVE_BODY_FIELDS = new Set([\n \"password\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apiKey\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"social_security\",\n]);\n\n/**\n * Default header filter function.\n */\nfunction defaultHeaderFilter(name: string): boolean {\n return !SENSITIVE_HEADERS.has(name.toLowerCase());\n}\n\n/**\n * Default body field filter function.\n */\nfunction defaultBodyFilter(key: string): boolean {\n return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());\n}\n\n/**\n * Filter an object's keys based on a filter function.\n */\nfunction filterObject<T extends Record<string, unknown>>(\n obj: T,\n filter: (key: string, value: unknown) => boolean\n): Partial<T> {\n const result: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (filter(key, value)) {\n (result as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Extract request context from a Fastify request.\n */\nfunction extractRequestContext(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): RequestContext {\n const headerFilter = options.filterHeaders || defaultHeaderFilter;\n const bodyFilter = options.filterBody || defaultBodyFilter;\n\n // Filter headers\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n if (typeof value === \"string\" && headerFilter(name, value)) {\n headers[name] = value;\n } else if (Array.isArray(value)) {\n const filtered = value.filter((v) => headerFilter(name, v));\n if (filtered.length > 0) {\n headers[name] = filtered.join(\", \");\n }\n }\n }\n\n const context: RequestContext = {\n url: request.url,\n method: request.method,\n headers,\n query_string: request.url.includes(\"?\") ? request.url.split(\"?\")[1] : undefined,\n };\n\n // Include body if requested and available\n if (options.includeBody && request.body && typeof request.body === \"object\") {\n context.data = filterObject(request.body as Record<string, unknown>, bodyFilter);\n }\n\n return context;\n}\n\n/**\n * Extract client IP from request.\n */\nfunction extractClientIp(request: FastifyRequest): string | undefined {\n // Check common proxy headers\n const forwarded = request.headers[\"x-forwarded-for\"];\n if (typeof forwarded === \"string\") {\n const firstIp = forwarded.split(\",\")[0]?.trim();\n if (firstIp) return firstIp;\n }\n\n const realIp = request.headers[\"x-real-ip\"];\n if (typeof realIp === \"string\") return realIp;\n\n const cfIp = request.headers[\"cf-connecting-ip\"];\n if (typeof cfIp === \"string\") return cfIp;\n\n // Fall back to connection address\n return request.ip;\n}\n\n/**\n * Create Bugwatch decorator for a request.\n */\nfunction createBugwatchDecorator(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): BugwatchDecorator {\n let eventId: string | undefined;\n const client = getClient();\n\n return {\n captureError(error: Error): string {\n if (!client) return \"\";\n\n const requestContext = extractRequestContext(request, options);\n const clientIp = extractClientIp(request);\n\n eventId = captureException(error, {\n request: requestContext,\n extra: {\n request: requestContext,\n ...(clientIp && { client_ip: clientIp }),\n },\n tags: {\n \"http.method\": request.method,\n \"http.url\": request.url,\n },\n });\n\n return eventId;\n },\n\n captureMessage(message: string, level: \"debug\" | \"info\" | \"warning\" | \"error\" = \"info\"): string {\n if (!client) return \"\";\n\n eventId = coreCaptureMessage(message, level);\n return eventId;\n },\n\n getEventId(): string | undefined {\n return eventId;\n },\n\n get client(): BugwatchClient | null {\n return client;\n },\n };\n}\n\n/**\n * Bugwatch plugin implementation.\n */\nconst bugwatchPluginImpl: FastifyPluginAsync<BugwatchFastifyOptions> = async (\n fastify,\n options\n) => {\n const opts: BugwatchFastifyOptions = {\n addBreadcrumbs: true,\n captureErrors: true,\n includeBody: false,\n flushOnError: false,\n ...options,\n };\n\n // Store request start times\n const requestStartTimes = new WeakMap<FastifyRequest, number>();\n\n // Decorate requests with bugwatch\n fastify.decorateRequest(\"bugwatch\", null);\n\n // onRequest hook — create scoped context and run the entire request lifecycle inside it.\n // We use runWithContext so that all downstream hooks and the route handler\n // can access the request-scoped context via getRequestContext().\n fastify.addHook(\"onRequest\", (request, _reply, done) => {\n const client = getClient();\n if (!client) {\n return done();\n }\n\n // Store start time\n requestStartTimes.set(request, Date.now());\n\n // Create a request-scoped context for isolation\n const scopedContext: ScopedContext = createScopedContext();\n\n // Create and attach decorator\n request.bugwatch = createBugwatchDecorator(request, opts);\n\n // Extract and set user context in the scoped context (not globally!)\n if (opts.extractUser) {\n const user = opts.extractUser(request);\n if (user) {\n scopedContext.user = user;\n }\n }\n\n // Run done() inside the scoped context so that all downstream\n // hooks and route handlers inherit the AsyncLocalStorage context\n runWithContext(scopedContext, () => {\n // Add request breadcrumb within the scoped context\n if (opts.addBreadcrumbs) {\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url}`,\n level: \"info\",\n data: {\n method: request.method,\n url: request.url,\n },\n });\n }\n\n done();\n });\n });\n\n // onResponse hook - add completion breadcrumb\n fastify.addHook(\"onResponse\", async (request, reply) => {\n if (!opts.addBreadcrumbs) return;\n\n const client = getClient();\n if (!client) return;\n\n const startTime = requestStartTimes.get(request);\n const duration = startTime ? Date.now() - startTime : undefined;\n\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url} -> ${reply.statusCode}`,\n level: reply.statusCode >= 500 ? \"error\" : reply.statusCode >= 400 ? \"warning\" : \"info\",\n data: {\n method: request.method,\n url: request.url,\n status_code: reply.statusCode,\n ...(duration !== undefined && { duration_ms: duration }),\n },\n });\n });\n\n // onError hook - capture errors\n fastify.addHook(\"onError\", async (request, _reply, error) => {\n if (!opts.captureErrors) return;\n\n const client = getClient();\n if (!client) return;\n\n // Capture the error\n request.bugwatch?.captureError(error);\n\n // Flush if requested\n if (opts.flushOnError) {\n await flush();\n }\n });\n\n // onClose hook - flush pending events on server shutdown\n fastify.addHook(\"onClose\", async () => {\n await close();\n });\n};\n\n/**\n * Bugwatch Fastify plugin.\n *\n * This plugin automatically:\n * - Captures request context for errors\n * - Adds HTTP request breadcrumbs\n * - Reports unhandled errors to Bugwatch\n * - Provides `request.bugwatch` decorator for manual error capture\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * // Initialize Bugwatch\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n *\n * // Register the plugin\n * await fastify.register(bugwatchPlugin, {\n * extractUser: (request) => {\n * // Extract user from your auth system\n * return request.user ? { id: request.user.id } : null;\n * },\n * });\n *\n * // Your routes\n * fastify.get(\"/\", async (request, reply) => {\n * return { hello: \"world\" };\n * });\n *\n * // Manual error capture\n * fastify.get(\"/risky\", async (request, reply) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport const bugwatchPlugin = fp(bugwatchPluginImpl, {\n fastify: \"4.x || 5.x\",\n name: \"@bugwatch/fastify\",\n});\n"],"mappings":"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","names":["import_core","coreCaptureMessage","fp","import_core"]}
package/dist/index.mjs CHANGED
@@ -9,6 +9,8 @@ import {
9
9
  captureMessage as coreCaptureMessage,
10
10
  addBreadcrumb,
11
11
  flush,
12
+ close,
13
+ runWithContext,
12
14
  createScopedContext
13
15
  } from "@bugwatch/core";
14
16
  var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
@@ -127,14 +129,14 @@ var bugwatchPluginImpl = async (fastify, options) => {
127
129
  ...options
128
130
  };
129
131
  const requestStartTimes = /* @__PURE__ */ new WeakMap();
130
- const requestContexts = /* @__PURE__ */ new WeakMap();
131
132
  fastify.decorateRequest("bugwatch", null);
132
- fastify.addHook("onRequest", async (request) => {
133
+ fastify.addHook("onRequest", (request, _reply, done) => {
133
134
  const client = getClient();
134
- if (!client) return;
135
+ if (!client) {
136
+ return done();
137
+ }
135
138
  requestStartTimes.set(request, Date.now());
136
139
  const scopedContext = createScopedContext();
137
- requestContexts.set(request, scopedContext);
138
140
  request.bugwatch = createBugwatchDecorator(request, opts);
139
141
  if (opts.extractUser) {
140
142
  const user = opts.extractUser(request);
@@ -142,17 +144,20 @@ var bugwatchPluginImpl = async (fastify, options) => {
142
144
  scopedContext.user = user;
143
145
  }
144
146
  }
145
- if (opts.addBreadcrumbs) {
146
- addBreadcrumb({
147
- category: "http",
148
- message: `${request.method} ${request.url}`,
149
- level: "info",
150
- data: {
151
- method: request.method,
152
- url: request.url
153
- }
154
- });
155
- }
147
+ runWithContext(scopedContext, () => {
148
+ if (opts.addBreadcrumbs) {
149
+ addBreadcrumb({
150
+ category: "http",
151
+ message: `${request.method} ${request.url}`,
152
+ level: "info",
153
+ data: {
154
+ method: request.method,
155
+ url: request.url
156
+ }
157
+ });
158
+ }
159
+ done();
160
+ });
156
161
  });
157
162
  fastify.addHook("onResponse", async (request, reply) => {
158
163
  if (!opts.addBreadcrumbs) return;
@@ -172,7 +177,7 @@ var bugwatchPluginImpl = async (fastify, options) => {
172
177
  }
173
178
  });
174
179
  });
175
- fastify.addHook("onError", async (request, reply, error) => {
180
+ fastify.addHook("onError", async (request, _reply, error) => {
176
181
  if (!opts.captureErrors) return;
177
182
  const client = getClient();
178
183
  if (!client) return;
@@ -181,6 +186,9 @@ var bugwatchPluginImpl = async (fastify, options) => {
181
186
  await flush();
182
187
  }
183
188
  });
189
+ fastify.addHook("onClose", async () => {
190
+ await close();
191
+ });
184
192
  };
185
193
  var bugwatchPlugin = fp(bugwatchPluginImpl, {
186
194
  fastify: "4.x || 5.x",
@@ -226,14 +234,14 @@ import {
226
234
  setTag,
227
235
  setExtra,
228
236
  flush as flush2,
229
- close
237
+ close as close2
230
238
  } from "@bugwatch/core";
231
239
  export {
232
240
  addBreadcrumb2 as addBreadcrumb,
233
241
  bugwatchPlugin,
234
242
  captureException2 as captureException,
235
243
  captureMessage,
236
- close,
244
+ close2 as close,
237
245
  flush2 as flush,
238
246
  getClient3 as getClient,
239
247
  init2 as init,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * One-liner setup for Bugwatch Fastify integration.\n *\n * This module provides a simplified setup function that handles all\n * initialization and plugin registration in a single call.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport { init, getClient, type BugwatchOptions } from \"@bugwatch/core\";\nimport { bugwatchPlugin } from \"./plugin\";\nimport type { BugwatchFastifyOptions } from \"./types\";\n\n/**\n * Combined options for Fastify setup.\n * Includes both core SDK options and Fastify-specific plugin options.\n */\nexport interface BugwatchFastifySetupOptions\n extends BugwatchFastifyOptions,\n Partial<BugwatchOptions> {}\n\n/**\n * Set up Bugwatch for a Fastify application with a single call.\n *\n * This function:\n * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)\n * 2. Registers the Bugwatch plugin with the Fastify instance\n *\n * @param fastify - The Fastify instance\n * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)\n * @returns Promise that resolves when the plugin is registered\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Minimal setup (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * environment: \"production\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport async function setup(\n fastify: FastifyInstance,\n options?: BugwatchFastifySetupOptions\n): Promise<void> {\n // Initialize core SDK if not already initialized\n if (!getClient()) {\n // Extract core options\n const coreOptions: Partial<BugwatchOptions> = {};\n if (options?.apiKey) coreOptions.apiKey = options.apiKey;\n if (options?.endpoint) coreOptions.endpoint = options.endpoint;\n if (options?.environment) coreOptions.environment = options.environment;\n if (options?.release) coreOptions.release = options.release;\n if (options?.debug !== undefined) coreOptions.debug = options.debug;\n if (options?.sampleRate !== undefined) coreOptions.sampleRate = options.sampleRate;\n if (options?.maxBreadcrumbs !== undefined) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;\n if (options?.tags) coreOptions.tags = options.tags;\n if (options?.user) coreOptions.user = options.user;\n if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;\n if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;\n\n init(coreOptions);\n }\n\n // Extract plugin-specific options\n const pluginOptions: BugwatchFastifyOptions = {};\n if (options?.extractUser) pluginOptions.extractUser = options.extractUser;\n if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;\n if (options?.filterBody) pluginOptions.filterBody = options.filterBody;\n if (options?.includeBody !== undefined) pluginOptions.includeBody = options.includeBody;\n if (options?.addBreadcrumbs !== undefined) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;\n if (options?.captureErrors !== undefined) pluginOptions.captureErrors = options.captureErrors;\n if (options?.flushOnError !== undefined) pluginOptions.flushOnError = options.flushOnError;\n\n // Register the plugin\n await fastify.register(bugwatchPlugin, pluginOptions);\n}\n","import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport {\n getClient,\n captureException,\n captureMessage as coreCaptureMessage,\n addBreadcrumb,\n setUser,\n flush,\n runWithContextAsync,\n createScopedContext,\n type RequestContext,\n type BugwatchClient,\n type ScopedContext,\n} from \"@bugwatch/core\";\nimport type { BugwatchFastifyOptions, BugwatchDecorator } from \"./types\";\n\n/**\n * Headers that should be filtered out by default for security.\n */\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n \"proxy-authorization\",\n]);\n\n/**\n * Body fields that should be filtered out by default for security.\n */\nconst SENSITIVE_BODY_FIELDS = new Set([\n \"password\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apiKey\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"social_security\",\n]);\n\n/**\n * Default header filter function.\n */\nfunction defaultHeaderFilter(name: string): boolean {\n return !SENSITIVE_HEADERS.has(name.toLowerCase());\n}\n\n/**\n * Default body field filter function.\n */\nfunction defaultBodyFilter(key: string): boolean {\n return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());\n}\n\n/**\n * Filter an object's keys based on a filter function.\n */\nfunction filterObject<T extends Record<string, unknown>>(\n obj: T,\n filter: (key: string, value: unknown) => boolean\n): Partial<T> {\n const result: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (filter(key, value)) {\n (result as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Extract request context from a Fastify request.\n */\nfunction extractRequestContext(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): RequestContext {\n const headerFilter = options.filterHeaders || defaultHeaderFilter;\n const bodyFilter = options.filterBody || defaultBodyFilter;\n\n // Filter headers\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n if (typeof value === \"string\" && headerFilter(name, value)) {\n headers[name] = value;\n } else if (Array.isArray(value)) {\n const filtered = value.filter((v) => headerFilter(name, v));\n if (filtered.length > 0) {\n headers[name] = filtered.join(\", \");\n }\n }\n }\n\n const context: RequestContext = {\n url: request.url,\n method: request.method,\n headers,\n query_string: request.url.includes(\"?\") ? request.url.split(\"?\")[1] : undefined,\n };\n\n // Include body if requested and available\n if (options.includeBody && request.body && typeof request.body === \"object\") {\n context.data = filterObject(request.body as Record<string, unknown>, bodyFilter);\n }\n\n return context;\n}\n\n/**\n * Extract client IP from request.\n */\nfunction extractClientIp(request: FastifyRequest): string | undefined {\n // Check common proxy headers\n const forwarded = request.headers[\"x-forwarded-for\"];\n if (typeof forwarded === \"string\") {\n const firstIp = forwarded.split(\",\")[0]?.trim();\n if (firstIp) return firstIp;\n }\n\n const realIp = request.headers[\"x-real-ip\"];\n if (typeof realIp === \"string\") return realIp;\n\n const cfIp = request.headers[\"cf-connecting-ip\"];\n if (typeof cfIp === \"string\") return cfIp;\n\n // Fall back to connection address\n return request.ip;\n}\n\n/**\n * Create Bugwatch decorator for a request.\n */\nfunction createBugwatchDecorator(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): BugwatchDecorator {\n let eventId: string | undefined;\n const client = getClient();\n\n return {\n captureError(error: Error): string {\n if (!client) return \"\";\n\n const requestContext = extractRequestContext(request, options);\n const clientIp = extractClientIp(request);\n\n eventId = captureException(error, {\n request: requestContext,\n extra: {\n request: requestContext,\n ...(clientIp && { client_ip: clientIp }),\n },\n tags: {\n \"http.method\": request.method,\n \"http.url\": request.url,\n },\n });\n\n return eventId;\n },\n\n captureMessage(message: string, level: \"debug\" | \"info\" | \"warning\" | \"error\" = \"info\"): string {\n if (!client) return \"\";\n\n eventId = coreCaptureMessage(message, level);\n return eventId;\n },\n\n getEventId(): string | undefined {\n return eventId;\n },\n\n get client(): BugwatchClient | null {\n return client;\n },\n };\n}\n\n/**\n * Bugwatch plugin implementation.\n */\nconst bugwatchPluginImpl: FastifyPluginAsync<BugwatchFastifyOptions> = async (\n fastify,\n options\n) => {\n const opts: BugwatchFastifyOptions = {\n addBreadcrumbs: true,\n captureErrors: true,\n includeBody: false,\n flushOnError: false,\n ...options,\n };\n\n // Store request start times and scoped contexts\n const requestStartTimes = new WeakMap<FastifyRequest, number>();\n const requestContexts = new WeakMap<FastifyRequest, ScopedContext>();\n\n // Decorate requests with bugwatch\n fastify.decorateRequest(\"bugwatch\", null);\n\n // onRequest hook - set up context\n fastify.addHook(\"onRequest\", async (request) => {\n const client = getClient();\n if (!client) return;\n\n // Store start time\n requestStartTimes.set(request, Date.now());\n\n // Create a request-scoped context for isolation\n const scopedContext: ScopedContext = createScopedContext();\n requestContexts.set(request, scopedContext);\n\n // Create and attach decorator\n request.bugwatch = createBugwatchDecorator(request, opts);\n\n // Extract and set user context in the scoped context (not globally!)\n if (opts.extractUser) {\n const user = opts.extractUser(request);\n if (user) {\n scopedContext.user = user;\n }\n }\n\n // Add request breadcrumb to scoped context\n if (opts.addBreadcrumbs) {\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url}`,\n level: \"info\",\n data: {\n method: request.method,\n url: request.url,\n },\n });\n }\n });\n\n // onResponse hook - add completion breadcrumb\n fastify.addHook(\"onResponse\", async (request, reply) => {\n if (!opts.addBreadcrumbs) return;\n\n const client = getClient();\n if (!client) return;\n\n const startTime = requestStartTimes.get(request);\n const duration = startTime ? Date.now() - startTime : undefined;\n\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url} -> ${reply.statusCode}`,\n level: reply.statusCode >= 500 ? \"error\" : reply.statusCode >= 400 ? \"warning\" : \"info\",\n data: {\n method: request.method,\n url: request.url,\n status_code: reply.statusCode,\n ...(duration !== undefined && { duration_ms: duration }),\n },\n });\n });\n\n // onError hook - capture errors\n fastify.addHook(\"onError\", async (request, reply, error) => {\n if (!opts.captureErrors) return;\n\n const client = getClient();\n if (!client) return;\n\n // Capture the error\n request.bugwatch?.captureError(error);\n\n // Flush if requested\n if (opts.flushOnError) {\n await flush();\n }\n });\n};\n\n/**\n * Bugwatch Fastify plugin.\n *\n * This plugin automatically:\n * - Captures request context for errors\n * - Adds HTTP request breadcrumbs\n * - Reports unhandled errors to Bugwatch\n * - Provides `request.bugwatch` decorator for manual error capture\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * // Initialize Bugwatch\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n *\n * // Register the plugin\n * await fastify.register(bugwatchPlugin, {\n * extractUser: (request) => {\n * // Extract user from your auth system\n * return request.user ? { id: request.user.id } : null;\n * },\n * });\n *\n * // Your routes\n * fastify.get(\"/\", async (request, reply) => {\n * return { hello: \"world\" };\n * });\n *\n * // Manual error capture\n * fastify.get(\"/risky\", async (request, reply) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport const bugwatchPlugin = fp(bugwatchPluginImpl, {\n fastify: \"4.x || 5.x\",\n name: \"@bugwatch/fastify\",\n});\n","/**\n * @bugwatch/fastify - Fastify integration for Bugwatch\n *\n * This package provides a plugin for automatically capturing errors\n * and request context in Fastify applications.\n *\n * @example One-liner setup (recommended)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Single call handles everything (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n *\n * @example Manual setup (for more control)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n * await fastify.register(bugwatchPlugin);\n *\n * // request.bugwatch is available for manual capture\n * fastify.get(\"/risky\", async (request) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\n\n// Export one-liner setup\nexport { setup } from \"./setup\";\nexport type { BugwatchFastifySetupOptions } from \"./setup\";\n\n// Export plugin\nexport { bugwatchPlugin } from \"./plugin\";\n\n// Export types\nexport type {\n BugwatchFastifyOptions,\n BugwatchDecorator,\n} from \"./types\";\n\n// Re-export core functions for convenience\nexport {\n init,\n getClient,\n captureException,\n captureMessage,\n addBreadcrumb,\n setUser,\n setTag,\n setExtra,\n flush,\n close,\n} from \"@bugwatch/core\";\n"],"mappings":"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","names":["getClient","getClient","init","getClient","captureException","addBreadcrumb","setUser","flush"]}
1
+ {"version":3,"sources":["../src/setup.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * One-liner setup for Bugwatch Fastify integration.\n *\n * This module provides a simplified setup function that handles all\n * initialization and plugin registration in a single call.\n */\n\nimport type { FastifyInstance } from \"fastify\";\nimport { init, getClient, type BugwatchOptions } from \"@bugwatch/core\";\nimport { bugwatchPlugin } from \"./plugin\";\nimport type { BugwatchFastifyOptions } from \"./types\";\n\n/**\n * Combined options for Fastify setup.\n * Includes both core SDK options and Fastify-specific plugin options.\n */\nexport interface BugwatchFastifySetupOptions\n extends BugwatchFastifyOptions,\n Partial<BugwatchOptions> {}\n\n/**\n * Set up Bugwatch for a Fastify application with a single call.\n *\n * This function:\n * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)\n * 2. Registers the Bugwatch plugin with the Fastify instance\n *\n * @param fastify - The Fastify instance\n * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)\n * @returns Promise that resolves when the plugin is registered\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Minimal setup (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * environment: \"production\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport async function setup(\n fastify: FastifyInstance,\n options?: BugwatchFastifySetupOptions\n): Promise<void> {\n // Initialize core SDK if not already initialized\n if (!getClient()) {\n // Extract core options\n const coreOptions: Partial<BugwatchOptions> = {};\n if (options?.apiKey) coreOptions.apiKey = options.apiKey;\n if (options?.endpoint) coreOptions.endpoint = options.endpoint;\n if (options?.environment) coreOptions.environment = options.environment;\n if (options?.release) coreOptions.release = options.release;\n if (options?.debug !== undefined) coreOptions.debug = options.debug;\n if (options?.sampleRate !== undefined) coreOptions.sampleRate = options.sampleRate;\n if (options?.maxBreadcrumbs !== undefined) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;\n if (options?.tags) coreOptions.tags = options.tags;\n if (options?.user) coreOptions.user = options.user;\n if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;\n if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;\n\n init(coreOptions);\n }\n\n // Extract plugin-specific options\n const pluginOptions: BugwatchFastifyOptions = {};\n if (options?.extractUser) pluginOptions.extractUser = options.extractUser;\n if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;\n if (options?.filterBody) pluginOptions.filterBody = options.filterBody;\n if (options?.includeBody !== undefined) pluginOptions.includeBody = options.includeBody;\n if (options?.addBreadcrumbs !== undefined) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;\n if (options?.captureErrors !== undefined) pluginOptions.captureErrors = options.captureErrors;\n if (options?.flushOnError !== undefined) pluginOptions.flushOnError = options.flushOnError;\n\n // Register the plugin\n await fastify.register(bugwatchPlugin, pluginOptions);\n}\n","import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport {\n getClient,\n captureException,\n captureMessage as coreCaptureMessage,\n addBreadcrumb,\n setUser,\n flush,\n close,\n runWithContext,\n createScopedContext,\n type RequestContext,\n type BugwatchClient,\n type ScopedContext,\n} from \"@bugwatch/core\";\nimport type { BugwatchFastifyOptions, BugwatchDecorator } from \"./types\";\n\n/**\n * Headers that should be filtered out by default for security.\n */\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n \"proxy-authorization\",\n]);\n\n/**\n * Body fields that should be filtered out by default for security.\n */\nconst SENSITIVE_BODY_FIELDS = new Set([\n \"password\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apiKey\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"social_security\",\n]);\n\n/**\n * Default header filter function.\n */\nfunction defaultHeaderFilter(name: string): boolean {\n return !SENSITIVE_HEADERS.has(name.toLowerCase());\n}\n\n/**\n * Default body field filter function.\n */\nfunction defaultBodyFilter(key: string): boolean {\n return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());\n}\n\n/**\n * Filter an object's keys based on a filter function.\n */\nfunction filterObject<T extends Record<string, unknown>>(\n obj: T,\n filter: (key: string, value: unknown) => boolean\n): Partial<T> {\n const result: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (filter(key, value)) {\n (result as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Extract request context from a Fastify request.\n */\nfunction extractRequestContext(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): RequestContext {\n const headerFilter = options.filterHeaders || defaultHeaderFilter;\n const bodyFilter = options.filterBody || defaultBodyFilter;\n\n // Filter headers\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n if (typeof value === \"string\" && headerFilter(name, value)) {\n headers[name] = value;\n } else if (Array.isArray(value)) {\n const filtered = value.filter((v) => headerFilter(name, v));\n if (filtered.length > 0) {\n headers[name] = filtered.join(\", \");\n }\n }\n }\n\n const context: RequestContext = {\n url: request.url,\n method: request.method,\n headers,\n query_string: request.url.includes(\"?\") ? request.url.split(\"?\")[1] : undefined,\n };\n\n // Include body if requested and available\n if (options.includeBody && request.body && typeof request.body === \"object\") {\n context.data = filterObject(request.body as Record<string, unknown>, bodyFilter);\n }\n\n return context;\n}\n\n/**\n * Extract client IP from request.\n */\nfunction extractClientIp(request: FastifyRequest): string | undefined {\n // Check common proxy headers\n const forwarded = request.headers[\"x-forwarded-for\"];\n if (typeof forwarded === \"string\") {\n const firstIp = forwarded.split(\",\")[0]?.trim();\n if (firstIp) return firstIp;\n }\n\n const realIp = request.headers[\"x-real-ip\"];\n if (typeof realIp === \"string\") return realIp;\n\n const cfIp = request.headers[\"cf-connecting-ip\"];\n if (typeof cfIp === \"string\") return cfIp;\n\n // Fall back to connection address\n return request.ip;\n}\n\n/**\n * Create Bugwatch decorator for a request.\n */\nfunction createBugwatchDecorator(\n request: FastifyRequest,\n options: BugwatchFastifyOptions\n): BugwatchDecorator {\n let eventId: string | undefined;\n const client = getClient();\n\n return {\n captureError(error: Error): string {\n if (!client) return \"\";\n\n const requestContext = extractRequestContext(request, options);\n const clientIp = extractClientIp(request);\n\n eventId = captureException(error, {\n request: requestContext,\n extra: {\n request: requestContext,\n ...(clientIp && { client_ip: clientIp }),\n },\n tags: {\n \"http.method\": request.method,\n \"http.url\": request.url,\n },\n });\n\n return eventId;\n },\n\n captureMessage(message: string, level: \"debug\" | \"info\" | \"warning\" | \"error\" = \"info\"): string {\n if (!client) return \"\";\n\n eventId = coreCaptureMessage(message, level);\n return eventId;\n },\n\n getEventId(): string | undefined {\n return eventId;\n },\n\n get client(): BugwatchClient | null {\n return client;\n },\n };\n}\n\n/**\n * Bugwatch plugin implementation.\n */\nconst bugwatchPluginImpl: FastifyPluginAsync<BugwatchFastifyOptions> = async (\n fastify,\n options\n) => {\n const opts: BugwatchFastifyOptions = {\n addBreadcrumbs: true,\n captureErrors: true,\n includeBody: false,\n flushOnError: false,\n ...options,\n };\n\n // Store request start times\n const requestStartTimes = new WeakMap<FastifyRequest, number>();\n\n // Decorate requests with bugwatch\n fastify.decorateRequest(\"bugwatch\", null);\n\n // onRequest hook — create scoped context and run the entire request lifecycle inside it.\n // We use runWithContext so that all downstream hooks and the route handler\n // can access the request-scoped context via getRequestContext().\n fastify.addHook(\"onRequest\", (request, _reply, done) => {\n const client = getClient();\n if (!client) {\n return done();\n }\n\n // Store start time\n requestStartTimes.set(request, Date.now());\n\n // Create a request-scoped context for isolation\n const scopedContext: ScopedContext = createScopedContext();\n\n // Create and attach decorator\n request.bugwatch = createBugwatchDecorator(request, opts);\n\n // Extract and set user context in the scoped context (not globally!)\n if (opts.extractUser) {\n const user = opts.extractUser(request);\n if (user) {\n scopedContext.user = user;\n }\n }\n\n // Run done() inside the scoped context so that all downstream\n // hooks and route handlers inherit the AsyncLocalStorage context\n runWithContext(scopedContext, () => {\n // Add request breadcrumb within the scoped context\n if (opts.addBreadcrumbs) {\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url}`,\n level: \"info\",\n data: {\n method: request.method,\n url: request.url,\n },\n });\n }\n\n done();\n });\n });\n\n // onResponse hook - add completion breadcrumb\n fastify.addHook(\"onResponse\", async (request, reply) => {\n if (!opts.addBreadcrumbs) return;\n\n const client = getClient();\n if (!client) return;\n\n const startTime = requestStartTimes.get(request);\n const duration = startTime ? Date.now() - startTime : undefined;\n\n addBreadcrumb({\n category: \"http\",\n message: `${request.method} ${request.url} -> ${reply.statusCode}`,\n level: reply.statusCode >= 500 ? \"error\" : reply.statusCode >= 400 ? \"warning\" : \"info\",\n data: {\n method: request.method,\n url: request.url,\n status_code: reply.statusCode,\n ...(duration !== undefined && { duration_ms: duration }),\n },\n });\n });\n\n // onError hook - capture errors\n fastify.addHook(\"onError\", async (request, _reply, error) => {\n if (!opts.captureErrors) return;\n\n const client = getClient();\n if (!client) return;\n\n // Capture the error\n request.bugwatch?.captureError(error);\n\n // Flush if requested\n if (opts.flushOnError) {\n await flush();\n }\n });\n\n // onClose hook - flush pending events on server shutdown\n fastify.addHook(\"onClose\", async () => {\n await close();\n });\n};\n\n/**\n * Bugwatch Fastify plugin.\n *\n * This plugin automatically:\n * - Captures request context for errors\n * - Adds HTTP request breadcrumbs\n * - Reports unhandled errors to Bugwatch\n * - Provides `request.bugwatch` decorator for manual error capture\n *\n * @example\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * // Initialize Bugwatch\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n *\n * // Register the plugin\n * await fastify.register(bugwatchPlugin, {\n * extractUser: (request) => {\n * // Extract user from your auth system\n * return request.user ? { id: request.user.id } : null;\n * },\n * });\n *\n * // Your routes\n * fastify.get(\"/\", async (request, reply) => {\n * return { hello: \"world\" };\n * });\n *\n * // Manual error capture\n * fastify.get(\"/risky\", async (request, reply) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport const bugwatchPlugin = fp(bugwatchPluginImpl, {\n fastify: \"4.x || 5.x\",\n name: \"@bugwatch/fastify\",\n});\n","/**\n * @bugwatch/fastify - Fastify integration for Bugwatch\n *\n * This package provides a plugin for automatically capturing errors\n * and request context in Fastify applications.\n *\n * @example One-liner setup (recommended)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { setup } from \"@bugwatch/fastify\";\n *\n * const fastify = Fastify();\n *\n * // Single call handles everything (uses BUGWATCH_API_KEY env var)\n * await setup(fastify);\n *\n * // Or with explicit options\n * await setup(fastify, {\n * apiKey: \"bw_live_xxxxx\",\n * extractUser: (request) => ({ id: request.user?.id }),\n * });\n *\n * fastify.get(\"/\", async () => ({ hello: \"world\" }));\n *\n * await fastify.listen({ port: 3000 });\n * ```\n *\n * @example Manual setup (for more control)\n * ```typescript\n * import Fastify from \"fastify\";\n * import { init } from \"@bugwatch/core\";\n * import { bugwatchPlugin } from \"@bugwatch/fastify\";\n *\n * init({ apiKey: \"your-api-key\" });\n *\n * const fastify = Fastify();\n * await fastify.register(bugwatchPlugin);\n *\n * // request.bugwatch is available for manual capture\n * fastify.get(\"/risky\", async (request) => {\n * try {\n * await riskyOperation();\n * } catch (err) {\n * request.bugwatch.captureError(err);\n * return { error: \"Something went wrong\" };\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\n\n// Export one-liner setup\nexport { setup } from \"./setup\";\nexport type { BugwatchFastifySetupOptions } from \"./setup\";\n\n// Export plugin\nexport { bugwatchPlugin } from \"./plugin\";\n\n// Export types\nexport type {\n BugwatchFastifyOptions,\n BugwatchDecorator,\n} from \"./types\";\n\n// Re-export core functions for convenience\nexport {\n init,\n getClient,\n captureException,\n captureMessage,\n addBreadcrumb,\n setUser,\n setTag,\n setExtra,\n flush,\n close,\n} from \"@bugwatch/core\";\n"],"mappings":"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","names":["getClient","getClient","init","getClient","captureException","addBreadcrumb","setUser","flush","close"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bugwatch/fastify",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Bugwatch SDK integration for Fastify",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",