snapwyr 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.
Files changed (50) hide show
  1. package/README.md +420 -0
  2. package/dist/dashboard.d.mts +1 -0
  3. package/dist/dashboard.d.ts +1 -0
  4. package/dist/dashboard.js +35 -0
  5. package/dist/dashboard.js.map +1 -0
  6. package/dist/dashboard.mjs +8 -0
  7. package/dist/dashboard.mjs.map +1 -0
  8. package/dist/express.d.mts +26 -0
  9. package/dist/express.d.ts +26 -0
  10. package/dist/express.js +203 -0
  11. package/dist/express.js.map +1 -0
  12. package/dist/express.mjs +183 -0
  13. package/dist/express.mjs.map +1 -0
  14. package/dist/fastify.d.mts +13 -0
  15. package/dist/fastify.d.ts +13 -0
  16. package/dist/fastify.js +204 -0
  17. package/dist/fastify.js.map +1 -0
  18. package/dist/fastify.mjs +184 -0
  19. package/dist/fastify.mjs.map +1 -0
  20. package/dist/hono.d.mts +18 -0
  21. package/dist/hono.d.ts +18 -0
  22. package/dist/hono.js +190 -0
  23. package/dist/hono.js.map +1 -0
  24. package/dist/hono.mjs +170 -0
  25. package/dist/hono.mjs.map +1 -0
  26. package/dist/index.d.mts +28 -0
  27. package/dist/index.d.ts +28 -0
  28. package/dist/index.js +78 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/index.mjs +51 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/koa.d.mts +21 -0
  33. package/dist/koa.d.ts +21 -0
  34. package/dist/koa.js +185 -0
  35. package/dist/koa.js.map +1 -0
  36. package/dist/koa.mjs +165 -0
  37. package/dist/koa.mjs.map +1 -0
  38. package/dist/nestjs.d.mts +19 -0
  39. package/dist/nestjs.d.ts +19 -0
  40. package/dist/nestjs.js +211 -0
  41. package/dist/nestjs.js.map +1 -0
  42. package/dist/nestjs.mjs +191 -0
  43. package/dist/nestjs.mjs.map +1 -0
  44. package/dist/nextjs.d.mts +33 -0
  45. package/dist/nextjs.d.ts +33 -0
  46. package/dist/nextjs.js +191 -0
  47. package/dist/nextjs.js.map +1 -0
  48. package/dist/nextjs.mjs +178 -0
  49. package/dist/nextjs.mjs.map +1 -0
  50. package/package.json +174 -0
package/README.md ADDED
@@ -0,0 +1,420 @@
1
+ # snapwyr
2
+
3
+ Zero-config HTTP request logger for Node.js with a real-time web dashboard.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install snapwyr
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Log Incoming Requests (Middleware)
14
+
15
+ ```javascript
16
+ import express from 'express';
17
+ import { snapwyr } from 'snapwyr/express';
18
+
19
+ const app = express();
20
+ app.use(snapwyr());
21
+
22
+ app.get('/api/users', (req, res) => res.json([]));
23
+ app.listen(3000);
24
+ ```
25
+
26
+ ### Log Outgoing Requests
27
+
28
+ ```javascript
29
+ import { logRequests } from 'snapwyr';
30
+
31
+ // Log fetch requests (automatic)
32
+ logRequests();
33
+ await fetch('https://api.example.com/users');
34
+
35
+ // To log axios requests, pass your axios instance
36
+ import axios from 'axios';
37
+ logRequests({ axios: axios });
38
+ await axios.get('https://api.example.com/users');
39
+ ```
40
+
41
+ ### Open the Dashboard
42
+
43
+ ```javascript
44
+ import { serve } from 'snapwyr/dashboard';
45
+
46
+ serve(3333); // http://localhost:3333
47
+ ```
48
+
49
+ ## Console Output
50
+
51
+ ```
52
+ 12:34:56.789 GET 200 45ms /api/users
53
+ 12:34:57.123 POST 201 89ms /api/users
54
+ 12:34:58.456 GET 404 12ms /api/unknown
55
+ ```
56
+
57
+ ## Framework Middleware
58
+
59
+ ### Express
60
+
61
+ ```javascript
62
+ import express from 'express';
63
+ import { snapwyr } from 'snapwyr/express';
64
+
65
+ const app = express();
66
+ app.use(snapwyr({ logBody: true }));
67
+ ```
68
+
69
+ ### Fastify
70
+
71
+ ```javascript
72
+ import Fastify from 'fastify';
73
+ import { snapwyr } from 'snapwyr/fastify';
74
+
75
+ const fastify = Fastify();
76
+ fastify.register(snapwyr, { logBody: true });
77
+ ```
78
+
79
+ ### Koa
80
+
81
+ ```javascript
82
+ import Koa from 'koa';
83
+ import { snapwyr } from 'snapwyr/koa';
84
+
85
+ const app = new Koa();
86
+ app.use(snapwyr({ logBody: true }));
87
+ ```
88
+
89
+ ### Hono
90
+
91
+ ```javascript
92
+ import { Hono } from 'hono';
93
+ import { snapwyr } from 'snapwyr/hono';
94
+
95
+ const app = new Hono();
96
+ app.use('*', snapwyr({ logBody: true }));
97
+ ```
98
+
99
+ ### NestJS
100
+
101
+ ```typescript
102
+ import { Module } from '@nestjs/common';
103
+ import { APP_INTERCEPTOR } from '@nestjs/core';
104
+ import { SnapwyrInterceptor } from 'snapwyr/nestjs';
105
+
106
+ @Module({
107
+ providers: [
108
+ {
109
+ provide: APP_INTERCEPTOR,
110
+ useValue: SnapwyrInterceptor({ logBody: true }),
111
+ },
112
+ ],
113
+ })
114
+ export class AppModule {}
115
+ ```
116
+
117
+ ### Next.js
118
+
119
+ Create `proxy.ts` in your project root:
120
+
121
+ ```typescript
122
+ import { snapwyr } from 'snapwyr/nextjs';
123
+
124
+ export const proxy = snapwyr({ logBody: true });
125
+
126
+ export const config = {
127
+ matcher: '/api/:path*',
128
+ };
129
+ ```
130
+
131
+ ## Dashboard
132
+
133
+ The dashboard provides a real-time web UI for viewing requests.
134
+
135
+ ```javascript
136
+ import express from 'express';
137
+ import { snapwyr } from 'snapwyr/express';
138
+ import { logRequests } from 'snapwyr';
139
+ import { serve } from 'snapwyr/dashboard';
140
+
141
+ const app = express();
142
+
143
+ // Log incoming requests
144
+ app.use(snapwyr({ logBody: true }));
145
+
146
+ // Log outgoing requests
147
+ logRequests({ logBody: true });
148
+
149
+ // Start dashboard
150
+ serve(3333);
151
+
152
+ app.listen(3000);
153
+ ```
154
+
155
+ Features:
156
+
157
+ - Live WebSocket updates
158
+ - Filter by method, status code, direction
159
+ - Search by URL or body content
160
+ - View request/response bodies
161
+ - Copy as cURL command
162
+ - Direction indicator (incoming vs outgoing)
163
+
164
+ ## Configuration
165
+
166
+ ```typescript
167
+ interface SnapWyrConfig {
168
+ // Output
169
+ format?: 'pretty' | 'json'; // Log format (default: 'pretty')
170
+ emoji?: boolean; // Show emoji indicators
171
+ silent?: boolean; // Disable console output
172
+ prefix?: string; // Prefix for log lines
173
+ showTimestamp?: boolean; // Show timestamps (default: true)
174
+
175
+ // Body logging
176
+ logBody?: boolean; // Log request/response bodies
177
+ bodySizeLimit?: number; // Max body size in bytes (default: 10KB)
178
+
179
+ // Filtering
180
+ errorsOnly?: boolean; // Only log 4xx/5xx responses
181
+ methods?: string[]; // Only log these HTTP methods
182
+ statusCodes?: number[]; // Only log these status codes
183
+ ignorePatterns?: (string | RegExp)[]; // URLs to ignore
184
+
185
+ // Features
186
+ slowThreshold?: number; // Mark slow requests (ms, default: 1000)
187
+ requestId?: boolean; // Generate X-Request-ID headers
188
+ sizeTracking?: boolean; // Track request/response sizes
189
+ redact?: (string | RegExp)[]; // Patterns to redact from bodies
190
+
191
+ // HTTP Clients
192
+ axios?: any; // Axios instance to intercept (required for axios logging)
193
+
194
+ // Advanced
195
+ transport?: (entry: LogEntry) => void; // Custom log handler
196
+ enabled?: boolean; // Enable/disable logging
197
+ }
198
+ ```
199
+
200
+ ## Examples
201
+
202
+ ### Log axios requests
203
+
204
+ ```javascript
205
+ import axios from 'axios';
206
+ import { logRequests } from 'snapwyr';
207
+
208
+ // Pass your axios instance to enable axios logging
209
+ logRequests({
210
+ axios: axios, // Required for axios interception
211
+ logBody: true,
212
+ });
213
+
214
+ // Custom axios instances also work
215
+ const apiClient = axios.create({ baseURL: 'https://api.example.com' });
216
+ logRequests({ axios: apiClient });
217
+ ```
218
+
219
+ ### Log bodies with size limit
220
+
221
+ ```javascript
222
+ snapwyr({
223
+ logBody: true,
224
+ bodySizeLimit: 5000, // 5KB max
225
+ });
226
+ ```
227
+
228
+ ### Redact sensitive data
229
+
230
+ ```javascript
231
+ snapwyr({
232
+ logBody: true,
233
+ redact: ['password', 'token', 'secret', /api[_-]?key/i],
234
+ });
235
+ ```
236
+
237
+ Output:
238
+
239
+ ```json
240
+ { "password": "[REDACTED]", "email": "user@example.com" }
241
+ ```
242
+
243
+ ### Generate request IDs
244
+
245
+ ```javascript
246
+ snapwyr({
247
+ requestId: true,
248
+ });
249
+ ```
250
+
251
+ Adds `X-Request-ID` header to responses and logs:
252
+
253
+ ```
254
+ [m2x9k-a3b] GET 200 45ms /api/users
255
+ ```
256
+
257
+ ### Track request sizes
258
+
259
+ ```javascript
260
+ snapwyr({
261
+ sizeTracking: true,
262
+ });
263
+ ```
264
+
265
+ Output:
266
+
267
+ ```
268
+ GET 200 45ms 2.4KB /api/users
269
+ ```
270
+
271
+ ### Filter by status code
272
+
273
+ ```javascript
274
+ snapwyr({
275
+ statusCodes: [500, 502, 503, 504], // Only log server errors
276
+ });
277
+ ```
278
+
279
+ ### JSON output
280
+
281
+ ```javascript
282
+ snapwyr({
283
+ format: 'json',
284
+ });
285
+ ```
286
+
287
+ Output:
288
+
289
+ ```json
290
+ {
291
+ "id": "m2x9k",
292
+ "timestamp": "2024-01-15T12:34:56.789Z",
293
+ "method": "GET",
294
+ "url": "/api/users",
295
+ "status": 200,
296
+ "duration": 45
297
+ }
298
+ ```
299
+
300
+ ### Custom transport
301
+
302
+ ```javascript
303
+ snapwyr({
304
+ transport: (entry) => {
305
+ // Send to external service
306
+ fetch('https://logs.example.com', {
307
+ method: 'POST',
308
+ body: JSON.stringify(entry),
309
+ });
310
+ },
311
+ });
312
+ ```
313
+
314
+ ### Ignore patterns
315
+
316
+ ```javascript
317
+ snapwyr({
318
+ ignorePatterns: ['/health', '/metrics', /^\/_next/],
319
+ });
320
+ ```
321
+
322
+ ## API
323
+
324
+ ### `logRequests(config?)`
325
+
326
+ Start logging outgoing HTTP requests (fetch, axios).
327
+
328
+ ```javascript
329
+ import { logRequests } from 'snapwyr';
330
+
331
+ // Log fetch requests (automatic)
332
+ logRequests();
333
+
334
+ // To log axios requests, pass your axios instance
335
+ import axios from 'axios';
336
+ logRequests({
337
+ axios: axios, // Required for axios interception
338
+ logBody: true,
339
+ });
340
+
341
+ // Or with a custom axios instance
342
+ const apiClient = axios.create({ baseURL: 'https://api.example.com' });
343
+ logRequests({
344
+ axios: apiClient, // Required for axios interception
345
+ });
346
+ ```
347
+
348
+ ### `stopLogging()`
349
+
350
+ Stop logging outgoing requests.
351
+
352
+ ```javascript
353
+ import { stopLogging } from 'snapwyr';
354
+ stopLogging();
355
+ ```
356
+
357
+ ### `toCurl(params)`
358
+
359
+ Generate a cURL command from request data.
360
+
361
+ ```javascript
362
+ import { toCurl } from 'snapwyr';
363
+
364
+ const curl = toCurl({
365
+ method: 'POST',
366
+ url: 'https://api.example.com/users',
367
+ headers: { 'Content-Type': 'application/json' },
368
+ body: '{"name":"John"}',
369
+ });
370
+ ```
371
+
372
+ ### `generateRequestId()`
373
+
374
+ Generate a unique request ID.
375
+
376
+ ```javascript
377
+ import { generateRequestId } from 'snapwyr';
378
+ const id = generateRequestId(); // "m2x9k-a3b7c"
379
+ ```
380
+
381
+ ### `serve(port, options?)`
382
+
383
+ Start the dashboard server.
384
+
385
+ ```javascript
386
+ import { serve } from 'snapwyr/dashboard';
387
+ serve(3333, { open: true });
388
+ ```
389
+
390
+ ### `stop()`
391
+
392
+ Stop the dashboard server.
393
+
394
+ ```javascript
395
+ import { stop } from 'snapwyr/dashboard';
396
+ stop();
397
+ ```
398
+
399
+ ## Production Safety
400
+
401
+ Snapwyr automatically disables itself when `NODE_ENV=production`:
402
+
403
+ ```javascript
404
+ // In production, this does nothing
405
+ logRequests();
406
+ ```
407
+
408
+ The middleware still works in production but logging is disabled by default. To enable:
409
+
410
+ ```javascript
411
+ snapwyr({ enabled: true });
412
+ ```
413
+
414
+ ## Requirements
415
+
416
+ - Node.js >= 18
417
+
418
+ ## License
419
+
420
+ AGPL-3.0
@@ -0,0 +1 @@
1
+ export { DashboardConfig, LogEntry, pushRequest, serve, stop } from '@snapwyr/dashboard';
@@ -0,0 +1 @@
1
+ export { DashboardConfig, LogEntry, pushRequest, serve, stop } from '@snapwyr/dashboard';
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/dashboard.ts
21
+ var dashboard_exports = {};
22
+ __export(dashboard_exports, {
23
+ pushRequest: () => import_dashboard.pushRequest,
24
+ serve: () => import_dashboard.serve,
25
+ stop: () => import_dashboard.stop
26
+ });
27
+ module.exports = __toCommonJS(dashboard_exports);
28
+ var import_dashboard = require("@snapwyr/dashboard");
29
+ // Annotate the CommonJS export names for ESM import in node:
30
+ 0 && (module.exports = {
31
+ pushRequest,
32
+ serve,
33
+ stop
34
+ });
35
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard.ts"],"sourcesContent":["export { serve, stop, pushRequest } from '@snapwyr/dashboard';\nexport type { DashboardConfig, LogEntry } from '@snapwyr/dashboard';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAyC;","names":[]}
@@ -0,0 +1,8 @@
1
+ // src/dashboard.ts
2
+ import { serve, stop, pushRequest } from "@snapwyr/dashboard";
3
+ export {
4
+ pushRequest,
5
+ serve,
6
+ stop
7
+ };
8
+ //# sourceMappingURL=dashboard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard.ts"],"sourcesContent":["export { serve, stop, pushRequest } from '@snapwyr/dashboard';\nexport type { DashboardConfig, LogEntry } from '@snapwyr/dashboard';\n"],"mappings":";AAAA,SAAS,OAAO,MAAM,mBAAmB;","names":[]}
@@ -0,0 +1,26 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, SnapWyrConfig } from '@snapwyr/core';
3
+
4
+ interface ExpressRequest {
5
+ method: string;
6
+ url: string;
7
+ originalUrl?: string;
8
+ body?: unknown;
9
+ headers?: Record<string, string | string[] | undefined>;
10
+ }
11
+ interface ExpressResponse {
12
+ statusCode: number;
13
+ send: (body?: unknown) => ExpressResponse;
14
+ setHeader: (name: string, value: string) => void;
15
+ }
16
+ type ExpressNextFunction = (err?: unknown) => void;
17
+ /**
18
+ * @example
19
+ * ```ts
20
+ * app.use(snapwyr());
21
+ * app.use(snapwyr({ format: 'json', emoji: true }));
22
+ * ```
23
+ */
24
+ declare function snapwyr(config?: SnapWyrConfig): (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
25
+
26
+ export { snapwyr };
@@ -0,0 +1,26 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, SnapWyrConfig } from '@snapwyr/core';
3
+
4
+ interface ExpressRequest {
5
+ method: string;
6
+ url: string;
7
+ originalUrl?: string;
8
+ body?: unknown;
9
+ headers?: Record<string, string | string[] | undefined>;
10
+ }
11
+ interface ExpressResponse {
12
+ statusCode: number;
13
+ send: (body?: unknown) => ExpressResponse;
14
+ setHeader: (name: string, value: string) => void;
15
+ }
16
+ type ExpressNextFunction = (err?: unknown) => void;
17
+ /**
18
+ * @example
19
+ * ```ts
20
+ * app.use(snapwyr());
21
+ * app.use(snapwyr({ format: 'json', emoji: true }));
22
+ * ```
23
+ */
24
+ declare function snapwyr(config?: SnapWyrConfig): (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
25
+
26
+ export { snapwyr };