@bugwatch/fastify 0.1.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.
@@ -0,0 +1,170 @@
1
+ import { FastifyRequest, FastifyInstance, FastifyPluginAsync } from 'fastify';
2
+ import { BugwatchClient, UserContext, BugwatchOptions } from '@bugwatch/core';
3
+ export { addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, setExtra, setTag, setUser } from '@bugwatch/core';
4
+
5
+ /**
6
+ * Options for the Bugwatch Fastify plugin
7
+ */
8
+ interface BugwatchFastifyOptions {
9
+ /**
10
+ * Extract user context from the request.
11
+ * Return null to skip user context extraction.
12
+ */
13
+ extractUser?: (request: FastifyRequest) => UserContext | null;
14
+ /**
15
+ * Filter headers before sending to Bugwatch.
16
+ * Return true to include the header, false to exclude.
17
+ * By default, sensitive headers are excluded.
18
+ */
19
+ filterHeaders?: (name: string, value: string) => boolean;
20
+ /**
21
+ * Filter body fields before sending to Bugwatch.
22
+ * Return true to include the field, false to exclude.
23
+ */
24
+ filterBody?: (key: string, value: unknown) => boolean;
25
+ /**
26
+ * Whether to include request body in error context.
27
+ * @default false
28
+ */
29
+ includeBody?: boolean;
30
+ /**
31
+ * Whether to add breadcrumbs for requests.
32
+ * @default true
33
+ */
34
+ addBreadcrumbs?: boolean;
35
+ /**
36
+ * Whether to automatically capture unhandled errors.
37
+ * @default true
38
+ */
39
+ captureErrors?: boolean;
40
+ /**
41
+ * Whether to flush events before sending error response.
42
+ * Useful for serverless environments.
43
+ * @default false
44
+ */
45
+ flushOnError?: boolean;
46
+ }
47
+ /**
48
+ * Bugwatch decorator interface for Fastify
49
+ */
50
+ interface BugwatchDecorator {
51
+ /**
52
+ * Capture an error with request context
53
+ */
54
+ captureError: (error: Error) => string;
55
+ /**
56
+ * Capture a message with request context
57
+ */
58
+ captureMessage: (message: string, level?: "debug" | "info" | "warning" | "error") => string;
59
+ /**
60
+ * Get the current event ID if an error was captured
61
+ */
62
+ getEventId: () => string | undefined;
63
+ /**
64
+ * Access the underlying Bugwatch client
65
+ */
66
+ client: BugwatchClient | null;
67
+ }
68
+ declare module "fastify" {
69
+ interface FastifyRequest {
70
+ bugwatch: BugwatchDecorator;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * One-liner setup for Bugwatch Fastify integration.
76
+ *
77
+ * This module provides a simplified setup function that handles all
78
+ * initialization and plugin registration in a single call.
79
+ */
80
+
81
+ /**
82
+ * Combined options for Fastify setup.
83
+ * Includes both core SDK options and Fastify-specific plugin options.
84
+ */
85
+ interface BugwatchFastifySetupOptions extends BugwatchFastifyOptions, Partial<BugwatchOptions> {
86
+ }
87
+ /**
88
+ * Set up Bugwatch for a Fastify application with a single call.
89
+ *
90
+ * This function:
91
+ * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)
92
+ * 2. Registers the Bugwatch plugin with the Fastify instance
93
+ *
94
+ * @param fastify - The Fastify instance
95
+ * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)
96
+ * @returns Promise that resolves when the plugin is registered
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * import Fastify from "fastify";
101
+ * import { setup } from "@bugwatch/fastify";
102
+ *
103
+ * const fastify = Fastify();
104
+ *
105
+ * // Minimal setup (uses BUGWATCH_API_KEY env var)
106
+ * await setup(fastify);
107
+ *
108
+ * // Or with explicit options
109
+ * await setup(fastify, {
110
+ * apiKey: "bw_live_xxxxx",
111
+ * environment: "production",
112
+ * extractUser: (request) => ({ id: request.user?.id }),
113
+ * });
114
+ *
115
+ * fastify.get("/", async () => ({ hello: "world" }));
116
+ *
117
+ * await fastify.listen({ port: 3000 });
118
+ * ```
119
+ */
120
+ declare function setup(fastify: FastifyInstance, options?: BugwatchFastifySetupOptions): Promise<void>;
121
+
122
+ /**
123
+ * Bugwatch Fastify plugin.
124
+ *
125
+ * This plugin automatically:
126
+ * - Captures request context for errors
127
+ * - Adds HTTP request breadcrumbs
128
+ * - Reports unhandled errors to Bugwatch
129
+ * - Provides `request.bugwatch` decorator for manual error capture
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * import Fastify from "fastify";
134
+ * import { init } from "@bugwatch/core";
135
+ * import { bugwatchPlugin } from "@bugwatch/fastify";
136
+ *
137
+ * // Initialize Bugwatch
138
+ * init({ apiKey: "your-api-key" });
139
+ *
140
+ * const fastify = Fastify();
141
+ *
142
+ * // Register the plugin
143
+ * await fastify.register(bugwatchPlugin, {
144
+ * extractUser: (request) => {
145
+ * // Extract user from your auth system
146
+ * return request.user ? { id: request.user.id } : null;
147
+ * },
148
+ * });
149
+ *
150
+ * // Your routes
151
+ * fastify.get("/", async (request, reply) => {
152
+ * return { hello: "world" };
153
+ * });
154
+ *
155
+ * // Manual error capture
156
+ * fastify.get("/risky", async (request, reply) => {
157
+ * try {
158
+ * await riskyOperation();
159
+ * } catch (err) {
160
+ * request.bugwatch.captureError(err);
161
+ * return { error: "Something went wrong" };
162
+ * }
163
+ * });
164
+ *
165
+ * await fastify.listen({ port: 3000 });
166
+ * ```
167
+ */
168
+ declare const bugwatchPlugin: FastifyPluginAsync<BugwatchFastifyOptions>;
169
+
170
+ export { type BugwatchDecorator, type BugwatchFastifyOptions, type BugwatchFastifySetupOptions, bugwatchPlugin, setup };
@@ -0,0 +1,170 @@
1
+ import { FastifyRequest, FastifyInstance, FastifyPluginAsync } from 'fastify';
2
+ import { BugwatchClient, UserContext, BugwatchOptions } from '@bugwatch/core';
3
+ export { addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, setExtra, setTag, setUser } from '@bugwatch/core';
4
+
5
+ /**
6
+ * Options for the Bugwatch Fastify plugin
7
+ */
8
+ interface BugwatchFastifyOptions {
9
+ /**
10
+ * Extract user context from the request.
11
+ * Return null to skip user context extraction.
12
+ */
13
+ extractUser?: (request: FastifyRequest) => UserContext | null;
14
+ /**
15
+ * Filter headers before sending to Bugwatch.
16
+ * Return true to include the header, false to exclude.
17
+ * By default, sensitive headers are excluded.
18
+ */
19
+ filterHeaders?: (name: string, value: string) => boolean;
20
+ /**
21
+ * Filter body fields before sending to Bugwatch.
22
+ * Return true to include the field, false to exclude.
23
+ */
24
+ filterBody?: (key: string, value: unknown) => boolean;
25
+ /**
26
+ * Whether to include request body in error context.
27
+ * @default false
28
+ */
29
+ includeBody?: boolean;
30
+ /**
31
+ * Whether to add breadcrumbs for requests.
32
+ * @default true
33
+ */
34
+ addBreadcrumbs?: boolean;
35
+ /**
36
+ * Whether to automatically capture unhandled errors.
37
+ * @default true
38
+ */
39
+ captureErrors?: boolean;
40
+ /**
41
+ * Whether to flush events before sending error response.
42
+ * Useful for serverless environments.
43
+ * @default false
44
+ */
45
+ flushOnError?: boolean;
46
+ }
47
+ /**
48
+ * Bugwatch decorator interface for Fastify
49
+ */
50
+ interface BugwatchDecorator {
51
+ /**
52
+ * Capture an error with request context
53
+ */
54
+ captureError: (error: Error) => string;
55
+ /**
56
+ * Capture a message with request context
57
+ */
58
+ captureMessage: (message: string, level?: "debug" | "info" | "warning" | "error") => string;
59
+ /**
60
+ * Get the current event ID if an error was captured
61
+ */
62
+ getEventId: () => string | undefined;
63
+ /**
64
+ * Access the underlying Bugwatch client
65
+ */
66
+ client: BugwatchClient | null;
67
+ }
68
+ declare module "fastify" {
69
+ interface FastifyRequest {
70
+ bugwatch: BugwatchDecorator;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * One-liner setup for Bugwatch Fastify integration.
76
+ *
77
+ * This module provides a simplified setup function that handles all
78
+ * initialization and plugin registration in a single call.
79
+ */
80
+
81
+ /**
82
+ * Combined options for Fastify setup.
83
+ * Includes both core SDK options and Fastify-specific plugin options.
84
+ */
85
+ interface BugwatchFastifySetupOptions extends BugwatchFastifyOptions, Partial<BugwatchOptions> {
86
+ }
87
+ /**
88
+ * Set up Bugwatch for a Fastify application with a single call.
89
+ *
90
+ * This function:
91
+ * 1. Initializes the Bugwatch SDK (using env vars if no apiKey provided)
92
+ * 2. Registers the Bugwatch plugin with the Fastify instance
93
+ *
94
+ * @param fastify - The Fastify instance
95
+ * @param options - Configuration options (optional if BUGWATCH_API_KEY env var is set)
96
+ * @returns Promise that resolves when the plugin is registered
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * import Fastify from "fastify";
101
+ * import { setup } from "@bugwatch/fastify";
102
+ *
103
+ * const fastify = Fastify();
104
+ *
105
+ * // Minimal setup (uses BUGWATCH_API_KEY env var)
106
+ * await setup(fastify);
107
+ *
108
+ * // Or with explicit options
109
+ * await setup(fastify, {
110
+ * apiKey: "bw_live_xxxxx",
111
+ * environment: "production",
112
+ * extractUser: (request) => ({ id: request.user?.id }),
113
+ * });
114
+ *
115
+ * fastify.get("/", async () => ({ hello: "world" }));
116
+ *
117
+ * await fastify.listen({ port: 3000 });
118
+ * ```
119
+ */
120
+ declare function setup(fastify: FastifyInstance, options?: BugwatchFastifySetupOptions): Promise<void>;
121
+
122
+ /**
123
+ * Bugwatch Fastify plugin.
124
+ *
125
+ * This plugin automatically:
126
+ * - Captures request context for errors
127
+ * - Adds HTTP request breadcrumbs
128
+ * - Reports unhandled errors to Bugwatch
129
+ * - Provides `request.bugwatch` decorator for manual error capture
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * import Fastify from "fastify";
134
+ * import { init } from "@bugwatch/core";
135
+ * import { bugwatchPlugin } from "@bugwatch/fastify";
136
+ *
137
+ * // Initialize Bugwatch
138
+ * init({ apiKey: "your-api-key" });
139
+ *
140
+ * const fastify = Fastify();
141
+ *
142
+ * // Register the plugin
143
+ * await fastify.register(bugwatchPlugin, {
144
+ * extractUser: (request) => {
145
+ * // Extract user from your auth system
146
+ * return request.user ? { id: request.user.id } : null;
147
+ * },
148
+ * });
149
+ *
150
+ * // Your routes
151
+ * fastify.get("/", async (request, reply) => {
152
+ * return { hello: "world" };
153
+ * });
154
+ *
155
+ * // Manual error capture
156
+ * fastify.get("/risky", async (request, reply) => {
157
+ * try {
158
+ * await riskyOperation();
159
+ * } catch (err) {
160
+ * request.bugwatch.captureError(err);
161
+ * return { error: "Something went wrong" };
162
+ * }
163
+ * });
164
+ *
165
+ * await fastify.listen({ port: 3000 });
166
+ * ```
167
+ */
168
+ declare const bugwatchPlugin: FastifyPluginAsync<BugwatchFastifyOptions>;
169
+
170
+ export { type BugwatchDecorator, type BugwatchFastifyOptions, type BugwatchFastifySetupOptions, bugwatchPlugin, setup };
package/dist/index.js ADDED
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ addBreadcrumb: () => import_core3.addBreadcrumb,
34
+ bugwatchPlugin: () => bugwatchPlugin,
35
+ captureException: () => import_core3.captureException,
36
+ captureMessage: () => import_core3.captureMessage,
37
+ close: () => import_core3.close,
38
+ flush: () => import_core3.flush,
39
+ getClient: () => import_core3.getClient,
40
+ init: () => import_core3.init,
41
+ setExtra: () => import_core3.setExtra,
42
+ setTag: () => import_core3.setTag,
43
+ setUser: () => import_core3.setUser,
44
+ setup: () => setup
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/setup.ts
49
+ var import_core2 = require("@bugwatch/core");
50
+
51
+ // src/plugin.ts
52
+ var import_fastify_plugin = __toESM(require("fastify-plugin"));
53
+ var import_core = require("@bugwatch/core");
54
+ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
55
+ "authorization",
56
+ "cookie",
57
+ "set-cookie",
58
+ "x-api-key",
59
+ "x-auth-token",
60
+ "x-csrf-token",
61
+ "x-xsrf-token",
62
+ "proxy-authorization"
63
+ ]);
64
+ var SENSITIVE_BODY_FIELDS = /* @__PURE__ */ new Set([
65
+ "password",
66
+ "secret",
67
+ "token",
68
+ "api_key",
69
+ "apiKey",
70
+ "credit_card",
71
+ "creditCard",
72
+ "ssn",
73
+ "social_security"
74
+ ]);
75
+ function defaultHeaderFilter(name) {
76
+ return !SENSITIVE_HEADERS.has(name.toLowerCase());
77
+ }
78
+ function defaultBodyFilter(key) {
79
+ return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());
80
+ }
81
+ function filterObject(obj, filter) {
82
+ const result = {};
83
+ for (const [key, value] of Object.entries(obj)) {
84
+ if (filter(key, value)) {
85
+ result[key] = value;
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+ function extractRequestContext(request, options) {
91
+ const headerFilter = options.filterHeaders || defaultHeaderFilter;
92
+ const bodyFilter = options.filterBody || defaultBodyFilter;
93
+ const headers = {};
94
+ for (const [name, value] of Object.entries(request.headers)) {
95
+ if (typeof value === "string" && headerFilter(name, value)) {
96
+ headers[name] = value;
97
+ } else if (Array.isArray(value)) {
98
+ const filtered = value.filter((v) => headerFilter(name, v));
99
+ if (filtered.length > 0) {
100
+ headers[name] = filtered.join(", ");
101
+ }
102
+ }
103
+ }
104
+ const context = {
105
+ url: request.url,
106
+ method: request.method,
107
+ headers,
108
+ query_string: request.url.includes("?") ? request.url.split("?")[1] : void 0
109
+ };
110
+ if (options.includeBody && request.body && typeof request.body === "object") {
111
+ context.data = filterObject(request.body, bodyFilter);
112
+ }
113
+ return context;
114
+ }
115
+ function extractClientIp(request) {
116
+ const forwarded = request.headers["x-forwarded-for"];
117
+ if (typeof forwarded === "string") {
118
+ const firstIp = forwarded.split(",")[0]?.trim();
119
+ if (firstIp) return firstIp;
120
+ }
121
+ const realIp = request.headers["x-real-ip"];
122
+ if (typeof realIp === "string") return realIp;
123
+ const cfIp = request.headers["cf-connecting-ip"];
124
+ if (typeof cfIp === "string") return cfIp;
125
+ return request.ip;
126
+ }
127
+ function createBugwatchDecorator(request, options) {
128
+ let eventId;
129
+ const client = (0, import_core.getClient)();
130
+ return {
131
+ captureError(error) {
132
+ if (!client) return "";
133
+ const requestContext = extractRequestContext(request, options);
134
+ const clientIp = extractClientIp(request);
135
+ eventId = (0, import_core.captureException)(error, {
136
+ request: requestContext,
137
+ extra: {
138
+ request: requestContext,
139
+ ...clientIp && { client_ip: clientIp }
140
+ },
141
+ tags: {
142
+ "http.method": request.method,
143
+ "http.url": request.url
144
+ }
145
+ });
146
+ return eventId;
147
+ },
148
+ captureMessage(message, level = "info") {
149
+ if (!client) return "";
150
+ eventId = (0, import_core.captureMessage)(message, level);
151
+ return eventId;
152
+ },
153
+ getEventId() {
154
+ return eventId;
155
+ },
156
+ get client() {
157
+ return client;
158
+ }
159
+ };
160
+ }
161
+ var bugwatchPluginImpl = async (fastify, options) => {
162
+ const opts = {
163
+ addBreadcrumbs: true,
164
+ captureErrors: true,
165
+ includeBody: false,
166
+ flushOnError: false,
167
+ ...options
168
+ };
169
+ const requestStartTimes = /* @__PURE__ */ new WeakMap();
170
+ const requestContexts = /* @__PURE__ */ new WeakMap();
171
+ fastify.decorateRequest("bugwatch", null);
172
+ fastify.addHook("onRequest", async (request) => {
173
+ const client = (0, import_core.getClient)();
174
+ if (!client) return;
175
+ requestStartTimes.set(request, Date.now());
176
+ const scopedContext = (0, import_core.createScopedContext)();
177
+ requestContexts.set(request, scopedContext);
178
+ request.bugwatch = createBugwatchDecorator(request, opts);
179
+ if (opts.extractUser) {
180
+ const user = opts.extractUser(request);
181
+ if (user) {
182
+ scopedContext.user = user;
183
+ }
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
+ }
196
+ });
197
+ fastify.addHook("onResponse", async (request, reply) => {
198
+ if (!opts.addBreadcrumbs) return;
199
+ const client = (0, import_core.getClient)();
200
+ if (!client) return;
201
+ const startTime = requestStartTimes.get(request);
202
+ const duration = startTime ? Date.now() - startTime : void 0;
203
+ (0, import_core.addBreadcrumb)({
204
+ category: "http",
205
+ message: `${request.method} ${request.url} -> ${reply.statusCode}`,
206
+ level: reply.statusCode >= 500 ? "error" : reply.statusCode >= 400 ? "warning" : "info",
207
+ data: {
208
+ method: request.method,
209
+ url: request.url,
210
+ status_code: reply.statusCode,
211
+ ...duration !== void 0 && { duration_ms: duration }
212
+ }
213
+ });
214
+ });
215
+ fastify.addHook("onError", async (request, reply, error) => {
216
+ if (!opts.captureErrors) return;
217
+ const client = (0, import_core.getClient)();
218
+ if (!client) return;
219
+ request.bugwatch?.captureError(error);
220
+ if (opts.flushOnError) {
221
+ await (0, import_core.flush)();
222
+ }
223
+ });
224
+ };
225
+ var bugwatchPlugin = (0, import_fastify_plugin.default)(bugwatchPluginImpl, {
226
+ fastify: "4.x || 5.x",
227
+ name: "@bugwatch/fastify"
228
+ });
229
+
230
+ // src/setup.ts
231
+ async function setup(fastify, options) {
232
+ if (!(0, import_core2.getClient)()) {
233
+ const coreOptions = {};
234
+ if (options?.apiKey) coreOptions.apiKey = options.apiKey;
235
+ if (options?.endpoint) coreOptions.endpoint = options.endpoint;
236
+ if (options?.environment) coreOptions.environment = options.environment;
237
+ if (options?.release) coreOptions.release = options.release;
238
+ if (options?.debug !== void 0) coreOptions.debug = options.debug;
239
+ if (options?.sampleRate !== void 0) coreOptions.sampleRate = options.sampleRate;
240
+ if (options?.maxBreadcrumbs !== void 0) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;
241
+ if (options?.tags) coreOptions.tags = options.tags;
242
+ if (options?.user) coreOptions.user = options.user;
243
+ if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;
244
+ if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;
245
+ (0, import_core2.init)(coreOptions);
246
+ }
247
+ const pluginOptions = {};
248
+ if (options?.extractUser) pluginOptions.extractUser = options.extractUser;
249
+ if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;
250
+ if (options?.filterBody) pluginOptions.filterBody = options.filterBody;
251
+ if (options?.includeBody !== void 0) pluginOptions.includeBody = options.includeBody;
252
+ if (options?.addBreadcrumbs !== void 0) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;
253
+ if (options?.captureErrors !== void 0) pluginOptions.captureErrors = options.captureErrors;
254
+ if (options?.flushOnError !== void 0) pluginOptions.flushOnError = options.flushOnError;
255
+ await fastify.register(bugwatchPlugin, pluginOptions);
256
+ }
257
+
258
+ // src/index.ts
259
+ var import_core3 = require("@bugwatch/core");
260
+ // Annotate the CommonJS export names for ESM import in node:
261
+ 0 && (module.exports = {
262
+ addBreadcrumb,
263
+ bugwatchPlugin,
264
+ captureException,
265
+ captureMessage,
266
+ close,
267
+ flush,
268
+ getClient,
269
+ init,
270
+ setExtra,
271
+ setTag,
272
+ setUser,
273
+ setup
274
+ });
275
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,245 @@
1
+ // src/setup.ts
2
+ import { init, getClient as getClient2 } from "@bugwatch/core";
3
+
4
+ // src/plugin.ts
5
+ import fp from "fastify-plugin";
6
+ import {
7
+ getClient,
8
+ captureException,
9
+ captureMessage as coreCaptureMessage,
10
+ addBreadcrumb,
11
+ flush,
12
+ createScopedContext
13
+ } from "@bugwatch/core";
14
+ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
15
+ "authorization",
16
+ "cookie",
17
+ "set-cookie",
18
+ "x-api-key",
19
+ "x-auth-token",
20
+ "x-csrf-token",
21
+ "x-xsrf-token",
22
+ "proxy-authorization"
23
+ ]);
24
+ var SENSITIVE_BODY_FIELDS = /* @__PURE__ */ new Set([
25
+ "password",
26
+ "secret",
27
+ "token",
28
+ "api_key",
29
+ "apiKey",
30
+ "credit_card",
31
+ "creditCard",
32
+ "ssn",
33
+ "social_security"
34
+ ]);
35
+ function defaultHeaderFilter(name) {
36
+ return !SENSITIVE_HEADERS.has(name.toLowerCase());
37
+ }
38
+ function defaultBodyFilter(key) {
39
+ return !SENSITIVE_BODY_FIELDS.has(key.toLowerCase());
40
+ }
41
+ function filterObject(obj, filter) {
42
+ const result = {};
43
+ for (const [key, value] of Object.entries(obj)) {
44
+ if (filter(key, value)) {
45
+ result[key] = value;
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+ function extractRequestContext(request, options) {
51
+ const headerFilter = options.filterHeaders || defaultHeaderFilter;
52
+ const bodyFilter = options.filterBody || defaultBodyFilter;
53
+ const headers = {};
54
+ for (const [name, value] of Object.entries(request.headers)) {
55
+ if (typeof value === "string" && headerFilter(name, value)) {
56
+ headers[name] = value;
57
+ } else if (Array.isArray(value)) {
58
+ const filtered = value.filter((v) => headerFilter(name, v));
59
+ if (filtered.length > 0) {
60
+ headers[name] = filtered.join(", ");
61
+ }
62
+ }
63
+ }
64
+ const context = {
65
+ url: request.url,
66
+ method: request.method,
67
+ headers,
68
+ query_string: request.url.includes("?") ? request.url.split("?")[1] : void 0
69
+ };
70
+ if (options.includeBody && request.body && typeof request.body === "object") {
71
+ context.data = filterObject(request.body, bodyFilter);
72
+ }
73
+ return context;
74
+ }
75
+ function extractClientIp(request) {
76
+ const forwarded = request.headers["x-forwarded-for"];
77
+ if (typeof forwarded === "string") {
78
+ const firstIp = forwarded.split(",")[0]?.trim();
79
+ if (firstIp) return firstIp;
80
+ }
81
+ const realIp = request.headers["x-real-ip"];
82
+ if (typeof realIp === "string") return realIp;
83
+ const cfIp = request.headers["cf-connecting-ip"];
84
+ if (typeof cfIp === "string") return cfIp;
85
+ return request.ip;
86
+ }
87
+ function createBugwatchDecorator(request, options) {
88
+ let eventId;
89
+ const client = getClient();
90
+ return {
91
+ captureError(error) {
92
+ if (!client) return "";
93
+ const requestContext = extractRequestContext(request, options);
94
+ const clientIp = extractClientIp(request);
95
+ eventId = captureException(error, {
96
+ request: requestContext,
97
+ extra: {
98
+ request: requestContext,
99
+ ...clientIp && { client_ip: clientIp }
100
+ },
101
+ tags: {
102
+ "http.method": request.method,
103
+ "http.url": request.url
104
+ }
105
+ });
106
+ return eventId;
107
+ },
108
+ captureMessage(message, level = "info") {
109
+ if (!client) return "";
110
+ eventId = coreCaptureMessage(message, level);
111
+ return eventId;
112
+ },
113
+ getEventId() {
114
+ return eventId;
115
+ },
116
+ get client() {
117
+ return client;
118
+ }
119
+ };
120
+ }
121
+ var bugwatchPluginImpl = async (fastify, options) => {
122
+ const opts = {
123
+ addBreadcrumbs: true,
124
+ captureErrors: true,
125
+ includeBody: false,
126
+ flushOnError: false,
127
+ ...options
128
+ };
129
+ const requestStartTimes = /* @__PURE__ */ new WeakMap();
130
+ const requestContexts = /* @__PURE__ */ new WeakMap();
131
+ fastify.decorateRequest("bugwatch", null);
132
+ fastify.addHook("onRequest", async (request) => {
133
+ const client = getClient();
134
+ if (!client) return;
135
+ requestStartTimes.set(request, Date.now());
136
+ const scopedContext = createScopedContext();
137
+ requestContexts.set(request, scopedContext);
138
+ request.bugwatch = createBugwatchDecorator(request, opts);
139
+ if (opts.extractUser) {
140
+ const user = opts.extractUser(request);
141
+ if (user) {
142
+ scopedContext.user = user;
143
+ }
144
+ }
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
+ }
156
+ });
157
+ fastify.addHook("onResponse", async (request, reply) => {
158
+ if (!opts.addBreadcrumbs) return;
159
+ const client = getClient();
160
+ if (!client) return;
161
+ const startTime = requestStartTimes.get(request);
162
+ const duration = startTime ? Date.now() - startTime : void 0;
163
+ addBreadcrumb({
164
+ category: "http",
165
+ message: `${request.method} ${request.url} -> ${reply.statusCode}`,
166
+ level: reply.statusCode >= 500 ? "error" : reply.statusCode >= 400 ? "warning" : "info",
167
+ data: {
168
+ method: request.method,
169
+ url: request.url,
170
+ status_code: reply.statusCode,
171
+ ...duration !== void 0 && { duration_ms: duration }
172
+ }
173
+ });
174
+ });
175
+ fastify.addHook("onError", async (request, reply, error) => {
176
+ if (!opts.captureErrors) return;
177
+ const client = getClient();
178
+ if (!client) return;
179
+ request.bugwatch?.captureError(error);
180
+ if (opts.flushOnError) {
181
+ await flush();
182
+ }
183
+ });
184
+ };
185
+ var bugwatchPlugin = fp(bugwatchPluginImpl, {
186
+ fastify: "4.x || 5.x",
187
+ name: "@bugwatch/fastify"
188
+ });
189
+
190
+ // src/setup.ts
191
+ async function setup(fastify, options) {
192
+ if (!getClient2()) {
193
+ const coreOptions = {};
194
+ if (options?.apiKey) coreOptions.apiKey = options.apiKey;
195
+ if (options?.endpoint) coreOptions.endpoint = options.endpoint;
196
+ if (options?.environment) coreOptions.environment = options.environment;
197
+ if (options?.release) coreOptions.release = options.release;
198
+ if (options?.debug !== void 0) coreOptions.debug = options.debug;
199
+ if (options?.sampleRate !== void 0) coreOptions.sampleRate = options.sampleRate;
200
+ if (options?.maxBreadcrumbs !== void 0) coreOptions.maxBreadcrumbs = options.maxBreadcrumbs;
201
+ if (options?.tags) coreOptions.tags = options.tags;
202
+ if (options?.user) coreOptions.user = options.user;
203
+ if (options?.beforeSend) coreOptions.beforeSend = options.beforeSend;
204
+ if (options?.ignoreErrors) coreOptions.ignoreErrors = options.ignoreErrors;
205
+ init(coreOptions);
206
+ }
207
+ const pluginOptions = {};
208
+ if (options?.extractUser) pluginOptions.extractUser = options.extractUser;
209
+ if (options?.filterHeaders) pluginOptions.filterHeaders = options.filterHeaders;
210
+ if (options?.filterBody) pluginOptions.filterBody = options.filterBody;
211
+ if (options?.includeBody !== void 0) pluginOptions.includeBody = options.includeBody;
212
+ if (options?.addBreadcrumbs !== void 0) pluginOptions.addBreadcrumbs = options.addBreadcrumbs;
213
+ if (options?.captureErrors !== void 0) pluginOptions.captureErrors = options.captureErrors;
214
+ if (options?.flushOnError !== void 0) pluginOptions.flushOnError = options.flushOnError;
215
+ await fastify.register(bugwatchPlugin, pluginOptions);
216
+ }
217
+
218
+ // src/index.ts
219
+ import {
220
+ init as init2,
221
+ getClient as getClient3,
222
+ captureException as captureException2,
223
+ captureMessage,
224
+ addBreadcrumb as addBreadcrumb2,
225
+ setUser as setUser2,
226
+ setTag,
227
+ setExtra,
228
+ flush as flush2,
229
+ close
230
+ } from "@bugwatch/core";
231
+ export {
232
+ addBreadcrumb2 as addBreadcrumb,
233
+ bugwatchPlugin,
234
+ captureException2 as captureException,
235
+ captureMessage,
236
+ close,
237
+ flush2 as flush,
238
+ getClient3 as getClient,
239
+ init2 as init,
240
+ setExtra,
241
+ setTag,
242
+ setUser2 as setUser,
243
+ setup
244
+ };
245
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@bugwatch/fastify",
3
+ "version": "0.1.0",
4
+ "description": "Bugwatch SDK integration for Fastify",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "clean": "rm -rf dist",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "@bugwatch/core": "*",
28
+ "fastify-plugin": "^4.5.1"
29
+ },
30
+ "peerDependencies": {
31
+ "fastify": "^4.0.0 || ^5.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "fastify": "^4.28.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.3.0"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/bugwatch/bugwatch.git",
44
+ "directory": "packages/sdk/fastify"
45
+ },
46
+ "license": "MIT",
47
+ "keywords": [
48
+ "bugwatch",
49
+ "error-tracking",
50
+ "fastify",
51
+ "plugin",
52
+ "monitoring"
53
+ ]
54
+ }