bugstack-sdk 1.0.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/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # bugstack-sdk
2
+
3
+ Error capture SDK for Next.js 14+ App Router API routes. Automatically captures errors with full context and reports them to the BugStack service for AI-powered fix generation.
4
+
5
+ **Get your API key at:** https://dashboard.bugstack.ai
6
+
7
+ ## Features
8
+
9
+ - **Zero-config wrapping** - Simply wrap your route handlers with `withErrorCapture`
10
+ - **Full error context** - Captures stack traces, request body, headers, query params
11
+ - **Automatic sanitization** - Sensitive data (passwords, tokens, etc.) is automatically redacted
12
+ - **Non-blocking** - Error reporting happens asynchronously without slowing down responses
13
+ - **TypeScript first** - Full type safety with comprehensive type definitions
14
+ - **Production ready** - Configurable timeouts, error handling, and kill switches
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install bugstack-sdk
20
+ # or
21
+ pnpm add bugstack-sdk
22
+ # or
23
+ yarn add bugstack-sdk
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### 1. Initialize the client (once, in a shared file)
29
+
30
+ ```typescript
31
+ // lib/bugstack.ts
32
+ import { ErrorCaptureClient } from "bugstack-sdk";
33
+
34
+ export const bugstack = ErrorCaptureClient.init({
35
+ apiKey: process.env.BUGSTACK_API_KEY!,
36
+ projectId: "my-project",
37
+ // Optional: customize endpoint
38
+ // endpoint: "https://your-bugstack-server.com/api/capture",
39
+ });
40
+ ```
41
+
42
+ ### 2. Wrap your API routes
43
+
44
+ ```typescript
45
+ // app/api/users/route.ts
46
+ import { NextResponse } from "next/server";
47
+ import { withErrorCapture } from "bugstack-sdk";
48
+ import "@/lib/bugstack"; // Ensure client is initialized
49
+
50
+ export const GET = withErrorCapture(async (request) => {
51
+ const users = await db.users.findMany();
52
+ return NextResponse.json({ users });
53
+ });
54
+
55
+ export const POST = withErrorCapture(async (request) => {
56
+ const body = await request.json();
57
+ const user = await db.users.create({ data: body });
58
+ return NextResponse.json({ user }, { status: 201 });
59
+ });
60
+ ```
61
+
62
+ ### 3. Dynamic routes with params
63
+
64
+ ```typescript
65
+ // app/api/users/[id]/route.ts
66
+ import { NextResponse } from "next/server";
67
+ import { withErrorCapture } from "bugstack-sdk";
68
+ import "@/lib/bugstack";
69
+
70
+ export const GET = withErrorCapture<{ id: string }>(
71
+ async (request, { params }) => {
72
+ const user = await db.users.findUnique({
73
+ where: { id: params.id },
74
+ });
75
+
76
+ if (!user) {
77
+ throw new Error("User not found");
78
+ }
79
+
80
+ return NextResponse.json({ user });
81
+ }
82
+ );
83
+ ```
84
+
85
+ ## Configuration
86
+
87
+ ### Full Configuration Options
88
+
89
+ ```typescript
90
+ import { ErrorCaptureClient } from "bugstack-sdk";
91
+
92
+ ErrorCaptureClient.init({
93
+ // Required
94
+ apiKey: "your-api-key",
95
+
96
+ // Optional - defaults shown
97
+ endpoint: "http://localhost:3001/api/capture",
98
+ projectId: "",
99
+ environment: process.env.NODE_ENV, // "development", "production", etc.
100
+ enabled: true, // Set to false to disable capture
101
+
102
+ // Request capture options
103
+ captureRequestBody: true,
104
+ captureQueryParams: true,
105
+ captureHeaders: true,
106
+ maxBodySize: 10000, // 10KB max body capture
107
+
108
+ // Additional fields to redact (added to defaults)
109
+ redactFields: ["customSecret", "internalToken"],
110
+
111
+ // Custom metadata included with every error
112
+ metadata: {
113
+ service: "api",
114
+ version: "1.0.0",
115
+ },
116
+
117
+ // Network options
118
+ timeout: 5000, // 5 second timeout for error reporting
119
+
120
+ // Debug logging (defaults to true in development)
121
+ debug: true,
122
+
123
+ // Custom filter - return false to skip certain errors
124
+ shouldCapture: (error, request) => {
125
+ // Don't capture 404s
126
+ if (error.message.includes("not found")) {
127
+ return false;
128
+ }
129
+ return true;
130
+ },
131
+
132
+ // Transform errors before sending
133
+ beforeSend: (capturedError) => {
134
+ // Add custom data
135
+ capturedError.metadata = {
136
+ ...capturedError.metadata,
137
+ customField: "value",
138
+ };
139
+ return capturedError;
140
+ // Return null to skip this error
141
+ },
142
+ });
143
+ ```
144
+
145
+ ### Environment Variables
146
+
147
+ The SDK automatically reads these environment variables:
148
+
149
+ ```bash
150
+ # Required
151
+ BUGSTACK_API_KEY=your-api-key
152
+
153
+ # Optional
154
+ BUGSTACK_ENDPOINT=https://bugstack-error-service.onrender.com/api/capture
155
+ BUGSTACK_PROJECT_ID=my-project
156
+ NODE_ENV=production
157
+ ```
158
+
159
+ **Note:** The default endpoint is `http://localhost:3001/api/capture` for local development. Set `BUGSTACK_ENDPOINT` to the production URL shown above for deployed applications.
160
+
161
+ ### Per-Route Options
162
+
163
+ You can override options for specific routes:
164
+
165
+ ```typescript
166
+ export const POST = withErrorCapture(
167
+ async (request) => {
168
+ // handler code
169
+ },
170
+ undefined, // Use existing client config
171
+ {
172
+ // Route-specific options
173
+ statusCode: 400, // Report as 400 instead of 500
174
+ metadata: {
175
+ operation: "createUser",
176
+ critical: true,
177
+ },
178
+ shouldCapture: (error) => {
179
+ // Route-specific filter
180
+ return error.name !== "ValidationError";
181
+ },
182
+ }
183
+ );
184
+ ```
185
+
186
+ ## Captured Error Data
187
+
188
+ Each error report includes:
189
+
190
+ ```typescript
191
+ interface CapturedError {
192
+ id: string; // Unique error ID (err_xxx_xxx)
193
+ name: string; // Error name (TypeError, etc.)
194
+ message: string; // Error message
195
+ stack: string; // Full stack trace
196
+ stackFrames: StackFrame[]; // Parsed stack frames
197
+ timestamp: string; // ISO 8601 timestamp
198
+ route: string; // API route path
199
+ method: string; // HTTP method
200
+ statusCode: number; // HTTP status code
201
+ url: string; // Full request URL
202
+ queryParams: object; // Query parameters (sanitized)
203
+ requestBody: unknown; // Request body (sanitized)
204
+ requestHeaders: object; // Headers (sanitized)
205
+ routeParams: object; // Dynamic route params
206
+ environment: string; // Environment name
207
+ projectId: string; // Project identifier
208
+ sdkVersion: string; // SDK version
209
+ nodeVersion: string; // Node.js version
210
+ nextVersion: string; // Next.js version
211
+ metadata: object; // Custom metadata
212
+ }
213
+ ```
214
+
215
+ ## Automatic Data Sanitization
216
+
217
+ The SDK automatically redacts sensitive fields from:
218
+ - Request headers (Authorization, Cookie, etc.)
219
+ - Request body
220
+ - Query parameters
221
+
222
+ **Default redacted fields:**
223
+ - password, secret, token
224
+ - apikey, api_key, apiKey
225
+ - authorization, auth, credential
226
+ - credit_card, creditcard, card_number
227
+ - cvv, ssn, social_security
228
+
229
+ Add custom fields via `redactFields` config option.
230
+
231
+ ## Error Handling
232
+
233
+ The SDK is designed to never interfere with your application:
234
+
235
+ - **Network failures** - Silently ignored, your app continues normally
236
+ - **Timeouts** - Configurable, defaults to 5 seconds
237
+ - **SDK errors** - Caught and logged (in debug mode), never thrown
238
+ - **Re-throws original error** - Next.js still handles the error response
239
+
240
+ ## Advanced Usage
241
+
242
+ ### Manual Error Reporting
243
+
244
+ ```typescript
245
+ import { ErrorCaptureClient } from "bugstack-sdk";
246
+
247
+ const client = ErrorCaptureClient.getInstance();
248
+
249
+ if (client) {
250
+ await client.reportError({
251
+ id: "manual_001",
252
+ name: "CustomError",
253
+ message: "Something went wrong",
254
+ timestamp: new Date().toISOString(),
255
+ route: "/api/custom",
256
+ method: "POST",
257
+ statusCode: 500,
258
+ url: "https://example.com/api/custom",
259
+ environment: "production",
260
+ sdkVersion: "1.0.0",
261
+ nodeVersion: process.version,
262
+ });
263
+ }
264
+ ```
265
+
266
+ ### Stack Trace Utilities
267
+
268
+ ```typescript
269
+ import {
270
+ parseStackTrace,
271
+ getFirstAppFrame,
272
+ getAppFrames,
273
+ } from "bugstack-sdk";
274
+
275
+ const frames = parseStackTrace(error.stack);
276
+ const firstAppFrame = getFirstAppFrame(frames); // First non-node_modules frame
277
+ const appFrames = getAppFrames(frames); // All application frames
278
+ ```
279
+
280
+ ### Sanitization Utilities
281
+
282
+ ```typescript
283
+ import {
284
+ sanitizeHeaders,
285
+ sanitizeQueryParams,
286
+ sanitizeObject,
287
+ } from "bugstack-sdk";
288
+
289
+ const safeHeaders = sanitizeHeaders(request.headers);
290
+ const safeParams = sanitizeQueryParams(new URL(request.url));
291
+ const safeBody = sanitizeObject(requestBody);
292
+ ```
293
+
294
+ ## TypeScript Support
295
+
296
+ Full type definitions are included:
297
+
298
+ ```typescript
299
+ import type {
300
+ ErrorCaptureConfig,
301
+ CapturedError,
302
+ StackFrame,
303
+ RouteOptions,
304
+ NextRouteHandler,
305
+ RouteContext,
306
+ } from "bugstack-sdk";
307
+ ```
308
+
309
+ ## Compatibility
310
+
311
+ - **Next.js**: 14.0.0+
312
+ - **Node.js**: 18.0.0+
313
+ - **TypeScript**: 5.0.0+ (optional)
314
+
315
+ ## License
316
+
317
+ MIT
@@ -0,0 +1,391 @@
1
+ import { NextRequest } from 'next/server';
2
+
3
+ /**
4
+ * Configuration options for the BugStack error capture SDK
5
+ */
6
+ interface ErrorCaptureConfig {
7
+ /**
8
+ * API key for authenticating with the BugStack error service
9
+ */
10
+ apiKey: string;
11
+ /**
12
+ * URL of the BugStack error service endpoint
13
+ * @default process.env.BUGSTACK_ENDPOINT || "http://localhost:3001/api/errors"
14
+ */
15
+ endpoint?: string;
16
+ /**
17
+ * Unique identifier for your project
18
+ */
19
+ projectId?: string;
20
+ /**
21
+ * Environment name (e.g., "production", "staging", "development")
22
+ * @default process.env.NODE_ENV || "development"
23
+ */
24
+ environment?: string;
25
+ /**
26
+ * Enable or disable error capture
27
+ * Useful for disabling in certain environments
28
+ * @default true
29
+ */
30
+ enabled?: boolean;
31
+ /**
32
+ * Whether to capture request body in error reports
33
+ * @default true
34
+ */
35
+ captureRequestBody?: boolean;
36
+ /**
37
+ * Whether to capture query parameters in error reports
38
+ * @default true
39
+ */
40
+ captureQueryParams?: boolean;
41
+ /**
42
+ * Whether to capture request headers in error reports
43
+ * @default true
44
+ */
45
+ captureHeaders?: boolean;
46
+ /**
47
+ * Maximum size of request body to capture (in bytes)
48
+ * Bodies larger than this will be truncated
49
+ * @default 10000 (10KB)
50
+ */
51
+ maxBodySize?: number;
52
+ /**
53
+ * Additional fields to redact from request body and headers
54
+ * Common sensitive fields are redacted by default
55
+ */
56
+ redactFields?: string[];
57
+ /**
58
+ * Custom metadata to include with every error report
59
+ */
60
+ metadata?: Record<string, unknown>;
61
+ /**
62
+ * Timeout for sending error reports (in milliseconds)
63
+ * @default 5000
64
+ */
65
+ timeout?: number;
66
+ /**
67
+ * Whether to log SDK errors to console
68
+ * @default true in development, false in production
69
+ */
70
+ debug?: boolean;
71
+ /**
72
+ * Custom error filter function
73
+ * Return false to skip reporting certain errors
74
+ */
75
+ shouldCapture?: (error: Error, request: NextRequest) => boolean;
76
+ /**
77
+ * Hook called before sending error report
78
+ * Can be used to modify or enrich error data
79
+ */
80
+ beforeSend?: (error: CapturedError) => CapturedError | null;
81
+ }
82
+ /**
83
+ * Resolved configuration with all defaults applied
84
+ */
85
+ interface ResolvedConfig {
86
+ apiKey: string;
87
+ endpoint: string;
88
+ projectId: string;
89
+ environment: string;
90
+ enabled: boolean;
91
+ captureRequestBody: boolean;
92
+ captureQueryParams: boolean;
93
+ captureHeaders: boolean;
94
+ maxBodySize: number;
95
+ redactFields: string[];
96
+ metadata: Record<string, unknown>;
97
+ timeout: number;
98
+ debug: boolean;
99
+ shouldCapture?: (error: Error, request: NextRequest) => boolean;
100
+ beforeSend?: (error: CapturedError) => CapturedError | null;
101
+ }
102
+ /**
103
+ * Captured error data sent to the error service
104
+ */
105
+ interface CapturedError {
106
+ /**
107
+ * Unique identifier for this error instance
108
+ */
109
+ id: string;
110
+ /**
111
+ * Error message
112
+ */
113
+ message: string;
114
+ /**
115
+ * Error name/type (e.g., "TypeError", "ReferenceError")
116
+ */
117
+ name: string;
118
+ /**
119
+ * Full stack trace
120
+ */
121
+ stack?: string;
122
+ /**
123
+ * Parsed stack frames for easier analysis
124
+ */
125
+ stackFrames?: StackFrame[];
126
+ /**
127
+ * ISO 8601 timestamp when the error occurred
128
+ */
129
+ timestamp: string;
130
+ /**
131
+ * API route path (e.g., "/api/users/[id]")
132
+ */
133
+ route: string;
134
+ /**
135
+ * HTTP method (GET, POST, etc.)
136
+ */
137
+ method: string;
138
+ /**
139
+ * HTTP status code
140
+ * @default 500
141
+ */
142
+ statusCode: number;
143
+ /**
144
+ * Full request URL
145
+ */
146
+ url: string;
147
+ /**
148
+ * Query parameters (sanitized)
149
+ */
150
+ queryParams?: Record<string, string>;
151
+ /**
152
+ * Request body (sanitized and truncated)
153
+ */
154
+ requestBody?: unknown;
155
+ /**
156
+ * Request headers (sanitized)
157
+ */
158
+ requestHeaders?: Record<string, string>;
159
+ /**
160
+ * Environment (production, staging, development)
161
+ */
162
+ environment: string;
163
+ /**
164
+ * Project identifier
165
+ */
166
+ projectId?: string;
167
+ /**
168
+ * Route parameters (e.g., { id: "123" })
169
+ */
170
+ routeParams?: Record<string, string>;
171
+ /**
172
+ * Additional custom metadata
173
+ */
174
+ metadata?: Record<string, unknown>;
175
+ /**
176
+ * SDK version that captured this error
177
+ */
178
+ sdkVersion: string;
179
+ /**
180
+ * Node.js version
181
+ */
182
+ nodeVersion: string;
183
+ /**
184
+ * Next.js version (if available)
185
+ */
186
+ nextVersion?: string;
187
+ }
188
+ /**
189
+ * Parsed stack frame for easier debugging
190
+ */
191
+ interface StackFrame {
192
+ /**
193
+ * Function name
194
+ */
195
+ function?: string;
196
+ /**
197
+ * File path
198
+ */
199
+ file?: string;
200
+ /**
201
+ * Line number
202
+ */
203
+ line?: number;
204
+ /**
205
+ * Column number
206
+ */
207
+ column?: number;
208
+ /**
209
+ * Whether this frame is from node_modules
210
+ */
211
+ isNodeModule?: boolean;
212
+ /**
213
+ * Whether this frame is from internal Node.js code
214
+ */
215
+ isInternal?: boolean;
216
+ }
217
+ /**
218
+ * Payload sent to the error service
219
+ */
220
+ interface ErrorReportPayload {
221
+ /**
222
+ * The captured error data
223
+ */
224
+ error: CapturedError;
225
+ /**
226
+ * Source code context around the error (if available)
227
+ */
228
+ sourceContext?: SourceContext;
229
+ }
230
+ /**
231
+ * Source code context around the error location
232
+ */
233
+ interface SourceContext {
234
+ /**
235
+ * File path
236
+ */
237
+ filePath: string;
238
+ /**
239
+ * Line number where error occurred
240
+ */
241
+ lineNumber: number;
242
+ /**
243
+ * Lines of code before the error line
244
+ */
245
+ preContext: string[];
246
+ /**
247
+ * The line where the error occurred
248
+ */
249
+ contextLine: string;
250
+ /**
251
+ * Lines of code after the error line
252
+ */
253
+ postContext: string[];
254
+ }
255
+ /**
256
+ * Next.js App Router route handler type
257
+ */
258
+ type NextRouteHandler<T = unknown> = (request: NextRequest, context: RouteContext<T>) => Response | Promise<Response>;
259
+ /**
260
+ * Route context passed to Next.js handlers
261
+ */
262
+ interface RouteContext<T = unknown> {
263
+ params: T extends Record<string, string> ? T : Record<string, string>;
264
+ }
265
+ /**
266
+ * Options that can be passed to withErrorCapture per-route
267
+ */
268
+ interface RouteOptions {
269
+ /**
270
+ * Override the status code reported for errors in this route
271
+ */
272
+ statusCode?: number;
273
+ /**
274
+ * Additional metadata specific to this route
275
+ */
276
+ metadata?: Record<string, unknown>;
277
+ /**
278
+ * Custom error filter for this route
279
+ */
280
+ shouldCapture?: (error: Error) => boolean;
281
+ }
282
+ type BugStackConfig = ErrorCaptureConfig;
283
+
284
+ /**
285
+ * Type for the wrapped handler function
286
+ */
287
+ type WrappedHandler<T> = (request: NextRequest, context: RouteContext<T>) => Response | Promise<Response>;
288
+ /**
289
+ * Wrap a Next.js App Router API route handler with error capture
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * // Basic usage
294
+ * export const GET = withErrorCapture(async (request) => {
295
+ * return NextResponse.json({ data: "hello" });
296
+ * });
297
+ *
298
+ * // With configuration (first call only)
299
+ * export const GET = withErrorCapture(
300
+ * async (request) => {
301
+ * return NextResponse.json({ data: "hello" });
302
+ * },
303
+ * { apiKey: "your-api-key" }
304
+ * );
305
+ *
306
+ * // With route-specific options
307
+ * export const POST = withErrorCapture(
308
+ * async (request) => {
309
+ * return NextResponse.json({ data: "created" });
310
+ * },
311
+ * undefined,
312
+ * { statusCode: 400, metadata: { operation: "create" } }
313
+ * );
314
+ * ```
315
+ */
316
+ declare function withErrorCapture<T = Record<string, string>>(handler: WrappedHandler<T>, config?: ErrorCaptureConfig, routeOptions?: RouteOptions): WrappedHandler<T>;
317
+ declare const withBugStack: typeof withErrorCapture;
318
+
319
+ /**
320
+ * BugStack Error Capture Client
321
+ *
322
+ * Singleton client for capturing and reporting errors to the BugStack service.
323
+ */
324
+ declare class ErrorCaptureClient {
325
+ private config;
326
+ private static instance;
327
+ constructor(config: ErrorCaptureConfig);
328
+ /**
329
+ * Initialize the singleton client instance
330
+ */
331
+ static init(config: ErrorCaptureConfig): ErrorCaptureClient;
332
+ /**
333
+ * Get the singleton client instance
334
+ */
335
+ static getInstance(): ErrorCaptureClient | null;
336
+ /**
337
+ * Check if the client has been initialized
338
+ */
339
+ static isInitialized(): boolean;
340
+ /**
341
+ * Reset the client (mainly for testing)
342
+ */
343
+ static reset(): void;
344
+ /**
345
+ * Report an error to the BugStack service
346
+ */
347
+ reportError(error: CapturedError, sourceContext?: SourceContext): Promise<boolean>;
348
+ /**
349
+ * Get the resolved configuration
350
+ */
351
+ getConfig(): ResolvedConfig;
352
+ /**
353
+ * Check if a field should be redacted
354
+ */
355
+ shouldRedact(fieldName: string): boolean;
356
+ /**
357
+ * Get the SDK version
358
+ */
359
+ static getVersion(): string;
360
+ private log;
361
+ private logError;
362
+ }
363
+ declare const BugStackClient: typeof ErrorCaptureClient;
364
+
365
+ /**
366
+ * Parse a V8 stack trace into structured frames
367
+ */
368
+ declare function parseStackTrace(stack: string | undefined): StackFrame[];
369
+ /**
370
+ * Get the first application frame (non-node_modules, non-internal)
371
+ */
372
+ declare function getFirstAppFrame(frames: StackFrame[]): StackFrame | undefined;
373
+ /**
374
+ * Filter stack frames to only include application code
375
+ */
376
+ declare function getAppFrames(frames: StackFrame[]): StackFrame[];
377
+
378
+ /**
379
+ * Sanitize headers by removing sensitive values
380
+ */
381
+ declare function sanitizeHeaders(headers: Headers, client?: ErrorCaptureClient | null): Record<string, string>;
382
+ /**
383
+ * Sanitize query parameters by redacting sensitive values
384
+ */
385
+ declare function sanitizeQueryParams(url: URL, client?: ErrorCaptureClient | null): Record<string, string>;
386
+ /**
387
+ * Recursively sanitize an object by redacting sensitive fields
388
+ */
389
+ declare function sanitizeObject(obj: unknown, client?: ErrorCaptureClient | null, maxDepth?: number, currentDepth?: number): unknown;
390
+
391
+ export { BugStackClient, type BugStackConfig, type CapturedError, ErrorCaptureClient, type ErrorCaptureConfig, type ErrorReportPayload, type NextRouteHandler, type ResolvedConfig, type RouteContext, type RouteOptions, type SourceContext, type StackFrame, getAppFrames, getFirstAppFrame, parseStackTrace, sanitizeHeaders, sanitizeObject, sanitizeQueryParams, withBugStack, withErrorCapture };