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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hono.ts","../src/utils.ts"],"sourcesContent":["import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport { generateRequestId } from '@snapwyr/core';\nimport { logRequest } from './utils.js';\n\ninterface HonoContext {\n req: {\n method: string;\n path: string;\n query: (key?: string) => string | Record<string, string> | undefined;\n raw: Request;\n };\n res: Response | undefined;\n header: (name: string, value: string) => void;\n}\ntype HonoNext = () => Promise<void>;\ntype HonoMiddleware = (\n c: HonoContext,\n next: HonoNext\n) => Promise<Response | void>;\n\nexport function snapwyr(config: SnapWyrConfig = {}): HonoMiddleware {\n return async function (c, next) {\n if (config.enabled === false) {\n await next();\n return;\n }\n\n const id = generateRequestId();\n const startTime = Date.now();\n const method = c.req.method;\n const queryObj = c.req.query();\n const queryString =\n typeof queryObj === 'object' && queryObj\n ? new URLSearchParams(queryObj as Record<string, string>).toString()\n : '';\n const url = c.req.path + (queryString ? '?' + queryString : '');\n\n if (config.requestId)\n try {\n c.header('X-Request-ID', id);\n } catch {}\n\n let requestBody: string | undefined;\n if (config.logBody) {\n try {\n const body = await c.req.raw\n .clone()\n .text()\n .catch(() => null);\n if (body) requestBody = body.slice(0, config.bodySizeLimit || 500);\n } catch {}\n }\n\n await next();\n\n const duration = Date.now() - startTime;\n const status = c.res?.status ?? 200;\n\n if (\n config.statusCodes &&\n config.statusCodes.length > 0 &&\n !config.statusCodes.includes(status)\n )\n return;\n\n let responseBody: string | undefined;\n if (config.logBody && c.res) {\n try {\n const resBody = await c.res\n .clone()\n .text()\n .catch(() => null);\n if (resBody)\n responseBody = resBody.slice(0, config.bodySizeLimit || 500);\n } catch {}\n }\n\n logRequest({\n id,\n method,\n status,\n duration,\n url,\n startTime,\n config,\n requestBody: config.logBody ? requestBody : undefined,\n responseBody: config.logBody ? responseBody : undefined,\n });\n };\n}\n\nexport type { SnapWyrConfig, LogEntry };\n","import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport {\n snapwyr as coreEmitter,\n getByteSize,\n formatBytes,\n redactSensitiveData,\n} from '@snapwyr/core';\n\nexport interface LogParams {\n id: string;\n method: string;\n status: number;\n duration: number;\n url: string;\n startTime: number;\n config: SnapWyrConfig;\n requestBody?: string;\n responseBody?: string;\n error?: string;\n}\n\nexport function logRequest(params: LogParams): void {\n const { id, method, status, duration, url, startTime, config, error } =\n params;\n let { requestBody, responseBody } = params;\n\n if (config.silent) return;\n\n const showTimestamp = config.showTimestamp !== false;\n const format = config.format || 'pretty';\n const slowThreshold = config.slowThreshold ?? 1000;\n const isSlow = duration >= slowThreshold;\n\n const requestSize = requestBody ? getByteSize(requestBody) : 0;\n const responseSize = responseBody ? getByteSize(responseBody) : 0;\n const totalSize = requestSize + responseSize;\n\n if (config.redact && config.redact.length > 0) {\n if (requestBody)\n requestBody = redactSensitiveData(requestBody, config.redact);\n if (responseBody)\n responseBody = redactSensitiveData(responseBody, config.redact);\n }\n\n const logEntry: LogEntry = {\n id,\n timestamp: new Date(startTime).toISOString(),\n method: method.toUpperCase(),\n url,\n status,\n duration,\n slow: isSlow,\n };\n\n if (config.prefix) logEntry.prefix = config.prefix;\n if (error) logEntry.error = error;\n if (requestBody) logEntry.requestBody = requestBody;\n if (responseBody) logEntry.responseBody = responseBody;\n if (config.sizeTracking) {\n logEntry.requestSize = requestSize;\n logEntry.responseSize = responseSize;\n logEntry.totalSize = totalSize;\n }\n\n try {\n coreEmitter.emit('request', {\n id,\n method: method.toUpperCase(),\n url,\n status,\n duration,\n timestamp: startTime,\n requestBody,\n responseBody,\n error,\n requestSize: config.sizeTracking ? requestSize : undefined,\n responseSize: config.sizeTracking ? responseSize : undefined,\n direction: 'incoming',\n });\n } catch {}\n\n if (config.transport) {\n config.transport(logEntry);\n }\n\n if (format === 'json') {\n console.log(JSON.stringify(logEntry));\n return;\n }\n\n const useEmoji = config.emoji === true;\n let statusEmoji = '';\n if (useEmoji) {\n if (error || status >= 500) statusEmoji = '✗ ';\n else if (status >= 400) statusEmoji = '⚠ ';\n else if (status >= 300) statusEmoji = '↪ ';\n else statusEmoji = '✓ ';\n }\n\n const statusColor =\n status >= 500\n ? '\\x1b[31m'\n : status >= 400\n ? '\\x1b[33m'\n : status >= 300\n ? '\\x1b[36m'\n : '\\x1b[32m';\n const durationColor = isSlow\n ? '\\x1b[31m'\n : duration < 100\n ? '\\x1b[32m'\n : '\\x1b[33m';\n const methodColors: Record<string, string> = {\n GET: '\\x1b[34m',\n POST: '\\x1b[32m',\n PUT: '\\x1b[33m',\n PATCH: '\\x1b[35m',\n DELETE: '\\x1b[31m',\n };\n const methodColor = methodColors[method] || '';\n const reset = '\\x1b[0m';\n const dim = '\\x1b[2m';\n const bold = '\\x1b[1m';\n const timestamp = showTimestamp\n ? new Date(startTime).toISOString().slice(11, 23) + ' '\n : '';\n const slowIndicator = isSlow ? ` ${bold}[SLOW]${reset}` : '';\n const requestIdDisplay = config.requestId ? `${dim}[${id}]${reset} ` : '';\n const sizeDisplay = config.sizeTracking\n ? `${dim}${formatBytes(totalSize)}${reset} `\n : '';\n\n const parts = [\n config.prefix ? `${dim}${config.prefix} ${reset}` : '',\n requestIdDisplay,\n `${dim}${timestamp}${reset}`,\n `${methodColor}${method.padEnd(6)}${reset}`,\n `${statusColor}${statusEmoji}${status}${reset}`,\n `${durationColor}${duration}ms${reset}${slowIndicator}`,\n sizeDisplay,\n `${dim}${url}${reset}`,\n ].filter(Boolean);\n\n if (error) parts.push(`\\n ${dim}Error: ${error}${reset}`);\n if (requestBody) parts.push(`\\n ${dim}Request: ${requestBody}${reset}`);\n if (responseBody) parts.push(`\\n ${dim}Response: ${responseBody}${reset}`);\n\n console.log(parts.join(' '));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,28 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, RequestEvent, SnapWyrConfig, TransportFn, formatBytes, generateRequestId as generateRequestIdFromCore, getByteSize, redactSensitiveData, toCurl as toCurlFromCore } from '@snapwyr/core';
3
+
4
+ /**
5
+ * @example
6
+ * ```ts
7
+ * import { logRequests } from 'snapwyr';
8
+ * logRequests();
9
+ * logRequests({ format: 'json', emoji: true });
10
+ * ```
11
+ */
12
+ declare function logRequests(config?: SnapWyrConfig): void;
13
+ declare function stopLogging(): void;
14
+ /**
15
+ * @example
16
+ * ```ts
17
+ * const curl = toCurl({ method: 'POST', url: '...', headers: {...}, body: '...' });
18
+ * ```
19
+ */
20
+ declare function toCurl(params: {
21
+ method: string;
22
+ url: string;
23
+ headers?: Record<string, string>;
24
+ body?: string;
25
+ }): string;
26
+ declare function generateRequestId(): string;
27
+
28
+ export { generateRequestId, logRequests, stopLogging, toCurl };
@@ -0,0 +1,28 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, RequestEvent, SnapWyrConfig, TransportFn, formatBytes, generateRequestId as generateRequestIdFromCore, getByteSize, redactSensitiveData, toCurl as toCurlFromCore } from '@snapwyr/core';
3
+
4
+ /**
5
+ * @example
6
+ * ```ts
7
+ * import { logRequests } from 'snapwyr';
8
+ * logRequests();
9
+ * logRequests({ format: 'json', emoji: true });
10
+ * ```
11
+ */
12
+ declare function logRequests(config?: SnapWyrConfig): void;
13
+ declare function stopLogging(): void;
14
+ /**
15
+ * @example
16
+ * ```ts
17
+ * const curl = toCurl({ method: 'POST', url: '...', headers: {...}, body: '...' });
18
+ * ```
19
+ */
20
+ declare function toCurl(params: {
21
+ method: string;
22
+ url: string;
23
+ headers?: Record<string, string>;
24
+ body?: string;
25
+ }): string;
26
+ declare function generateRequestId(): string;
27
+
28
+ export { generateRequestId, logRequests, stopLogging, toCurl };
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ formatBytes: () => import_core2.formatBytes,
24
+ generateRequestId: () => generateRequestId2,
25
+ generateRequestIdFromCore: () => import_core2.generateRequestId,
26
+ getByteSize: () => import_core2.getByteSize,
27
+ logRequests: () => logRequests,
28
+ redactSensitiveData: () => import_core2.redactSensitiveData,
29
+ stopLogging: () => stopLogging,
30
+ toCurl: () => toCurl2,
31
+ toCurlFromCore: () => import_core2.toCurl
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+ var import_core = require("@snapwyr/core");
35
+ var import_core2 = require("@snapwyr/core");
36
+ function logRequests(config = {}) {
37
+ import_core.snapwyr.start(config);
38
+ }
39
+ function stopLogging() {
40
+ import_core.snapwyr.stop();
41
+ }
42
+ function toCurl2(params) {
43
+ const { method, url, headers, body } = params;
44
+ const parts = ["curl"];
45
+ if (method.toUpperCase() !== "GET") {
46
+ parts.push(`-X ${method.toUpperCase()}`);
47
+ }
48
+ if (headers) {
49
+ for (const [key, value] of Object.entries(headers)) {
50
+ if (key.startsWith(":") || key.toLowerCase() === "authorization") {
51
+ continue;
52
+ }
53
+ parts.push(`-H '${key}: ${value}'`);
54
+ }
55
+ }
56
+ if (body && method.toUpperCase() !== "GET") {
57
+ const escapedBody = body.replace(/'/g, "'\\''");
58
+ parts.push(`-d '${escapedBody}'`);
59
+ }
60
+ parts.push(`'${url}'`);
61
+ return parts.join(" \\\n ");
62
+ }
63
+ function generateRequestId2() {
64
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
65
+ }
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ formatBytes,
69
+ generateRequestId,
70
+ generateRequestIdFromCore,
71
+ getByteSize,
72
+ logRequests,
73
+ redactSensitiveData,
74
+ stopLogging,
75
+ toCurl,
76
+ toCurlFromCore
77
+ });
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { snapwyr, SnapWyrConfig } from '@snapwyr/core';\n\n/**\n * @example\n * ```ts\n * import { logRequests } from 'snapwyr';\n * logRequests();\n * logRequests({ format: 'json', emoji: true });\n * ```\n */\nexport function logRequests(config: SnapWyrConfig = {}): void {\n snapwyr.start(config);\n}\n\nexport function stopLogging(): void {\n snapwyr.stop();\n}\n\n/**\n * @example\n * ```ts\n * const curl = toCurl({ method: 'POST', url: '...', headers: {...}, body: '...' });\n * ```\n */\nexport function toCurl(params: {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: string;\n}): string {\n const { method, url, headers, body } = params;\n const parts = ['curl'];\n\n if (method.toUpperCase() !== 'GET') {\n parts.push(`-X ${method.toUpperCase()}`);\n }\n\n if (headers) {\n for (const [key, value] of Object.entries(headers)) {\n if (key.startsWith(':') || key.toLowerCase() === 'authorization') {\n continue;\n }\n parts.push(`-H '${key}: ${value}'`);\n }\n }\n\n if (body && method.toUpperCase() !== 'GET') {\n const escapedBody = body.replace(/'/g, \"'\\\\''\");\n parts.push(`-d '${escapedBody}'`);\n }\n\n parts.push(`'${url}'`);\n\n return parts.join(' \\\\\\n ');\n}\n\nexport function generateRequestId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport {\n toCurl as toCurlFromCore,\n generateRequestId as generateRequestIdFromCore,\n redactSensitiveData,\n formatBytes,\n getByteSize,\n} from '@snapwyr/core';\nexport type {\n SnapWyrConfig,\n RequestEvent,\n LogEntry,\n TransportFn,\n} from '@snapwyr/core';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA,2BAAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAAC;AAAA,EAAA;AAAA;AAAA;AAAA,kBAAuC;AA4DvC,IAAAC,eAMO;AAxDA,SAAS,YAAY,SAAwB,CAAC,GAAS;AAC5D,sBAAQ,MAAM,MAAM;AACtB;AAEO,SAAS,cAAoB;AAClC,sBAAQ,KAAK;AACf;AAQO,SAASD,QAAO,QAKZ;AACT,QAAM,EAAE,QAAQ,KAAK,SAAS,KAAK,IAAI;AACvC,QAAM,QAAQ,CAAC,MAAM;AAErB,MAAI,OAAO,YAAY,MAAM,OAAO;AAClC,UAAM,KAAK,MAAM,OAAO,YAAY,CAAC,EAAE;AAAA,EACzC;AAEA,MAAI,SAAS;AACX,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAI,IAAI,WAAW,GAAG,KAAK,IAAI,YAAY,MAAM,iBAAiB;AAChE;AAAA,MACF;AACA,YAAM,KAAK,OAAO,GAAG,KAAK,KAAK,GAAG;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,QAAQ,OAAO,YAAY,MAAM,OAAO;AAC1C,UAAM,cAAc,KAAK,QAAQ,MAAM,OAAO;AAC9C,UAAM,KAAK,OAAO,WAAW,GAAG;AAAA,EAClC;AAEA,QAAM,KAAK,IAAI,GAAG,GAAG;AAErB,SAAO,MAAM,KAAK,SAAS;AAC7B;AAEO,SAASD,qBAA4B;AAC1C,SAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC7E;","names":["generateRequestId","toCurl","import_core"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,51 @@
1
+ // src/index.ts
2
+ import { snapwyr } from "@snapwyr/core";
3
+ import {
4
+ toCurl,
5
+ generateRequestId,
6
+ redactSensitiveData,
7
+ formatBytes,
8
+ getByteSize
9
+ } from "@snapwyr/core";
10
+ function logRequests(config = {}) {
11
+ snapwyr.start(config);
12
+ }
13
+ function stopLogging() {
14
+ snapwyr.stop();
15
+ }
16
+ function toCurl2(params) {
17
+ const { method, url, headers, body } = params;
18
+ const parts = ["curl"];
19
+ if (method.toUpperCase() !== "GET") {
20
+ parts.push(`-X ${method.toUpperCase()}`);
21
+ }
22
+ if (headers) {
23
+ for (const [key, value] of Object.entries(headers)) {
24
+ if (key.startsWith(":") || key.toLowerCase() === "authorization") {
25
+ continue;
26
+ }
27
+ parts.push(`-H '${key}: ${value}'`);
28
+ }
29
+ }
30
+ if (body && method.toUpperCase() !== "GET") {
31
+ const escapedBody = body.replace(/'/g, "'\\''");
32
+ parts.push(`-d '${escapedBody}'`);
33
+ }
34
+ parts.push(`'${url}'`);
35
+ return parts.join(" \\\n ");
36
+ }
37
+ function generateRequestId2() {
38
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
39
+ }
40
+ export {
41
+ formatBytes,
42
+ generateRequestId2 as generateRequestId,
43
+ generateRequestId as generateRequestIdFromCore,
44
+ getByteSize,
45
+ logRequests,
46
+ redactSensitiveData,
47
+ stopLogging,
48
+ toCurl2 as toCurl,
49
+ toCurl as toCurlFromCore
50
+ };
51
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { snapwyr, SnapWyrConfig } from '@snapwyr/core';\n\n/**\n * @example\n * ```ts\n * import { logRequests } from 'snapwyr';\n * logRequests();\n * logRequests({ format: 'json', emoji: true });\n * ```\n */\nexport function logRequests(config: SnapWyrConfig = {}): void {\n snapwyr.start(config);\n}\n\nexport function stopLogging(): void {\n snapwyr.stop();\n}\n\n/**\n * @example\n * ```ts\n * const curl = toCurl({ method: 'POST', url: '...', headers: {...}, body: '...' });\n * ```\n */\nexport function toCurl(params: {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: string;\n}): string {\n const { method, url, headers, body } = params;\n const parts = ['curl'];\n\n if (method.toUpperCase() !== 'GET') {\n parts.push(`-X ${method.toUpperCase()}`);\n }\n\n if (headers) {\n for (const [key, value] of Object.entries(headers)) {\n if (key.startsWith(':') || key.toLowerCase() === 'authorization') {\n continue;\n }\n parts.push(`-H '${key}: ${value}'`);\n }\n }\n\n if (body && method.toUpperCase() !== 'GET') {\n const escapedBody = body.replace(/'/g, \"'\\\\''\");\n parts.push(`-d '${escapedBody}'`);\n }\n\n parts.push(`'${url}'`);\n\n return parts.join(' \\\\\\n ');\n}\n\nexport function generateRequestId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport {\n toCurl as toCurlFromCore,\n generateRequestId as generateRequestIdFromCore,\n redactSensitiveData,\n formatBytes,\n getByteSize,\n} from '@snapwyr/core';\nexport type {\n SnapWyrConfig,\n RequestEvent,\n LogEntry,\n TransportFn,\n} from '@snapwyr/core';\n"],"mappings":";AAAA,SAAS,eAA8B;AA4DvC;AAAA,EACY;AAAA,EACW;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAxDA,SAAS,YAAY,SAAwB,CAAC,GAAS;AAC5D,UAAQ,MAAM,MAAM;AACtB;AAEO,SAAS,cAAoB;AAClC,UAAQ,KAAK;AACf;AAQO,SAASA,QAAO,QAKZ;AACT,QAAM,EAAE,QAAQ,KAAK,SAAS,KAAK,IAAI;AACvC,QAAM,QAAQ,CAAC,MAAM;AAErB,MAAI,OAAO,YAAY,MAAM,OAAO;AAClC,UAAM,KAAK,MAAM,OAAO,YAAY,CAAC,EAAE;AAAA,EACzC;AAEA,MAAI,SAAS;AACX,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAI,IAAI,WAAW,GAAG,KAAK,IAAI,YAAY,MAAM,iBAAiB;AAChE;AAAA,MACF;AACA,YAAM,KAAK,OAAO,GAAG,KAAK,KAAK,GAAG;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,QAAQ,OAAO,YAAY,MAAM,OAAO;AAC1C,UAAM,cAAc,KAAK,QAAQ,MAAM,OAAO;AAC9C,UAAM,KAAK,OAAO,WAAW,GAAG;AAAA,EAClC;AAEA,QAAM,KAAK,IAAI,GAAG,GAAG;AAErB,SAAO,MAAM,KAAK,SAAS;AAC7B;AAEO,SAASC,qBAA4B;AAC1C,SAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC7E;","names":["toCurl","generateRequestId"]}
package/dist/koa.d.mts ADDED
@@ -0,0 +1,21 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, SnapWyrConfig } from '@snapwyr/core';
3
+
4
+ interface KoaRequest {
5
+ method: string;
6
+ url: string;
7
+ body?: unknown;
8
+ }
9
+ interface KoaContext {
10
+ method: string;
11
+ url: string;
12
+ status: number;
13
+ request: KoaRequest;
14
+ body?: unknown;
15
+ set: (field: string, val: string) => void;
16
+ }
17
+ type KoaNext = () => Promise<void>;
18
+ type KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;
19
+ declare function snapwyr(config?: SnapWyrConfig): KoaMiddleware;
20
+
21
+ export { snapwyr };
package/dist/koa.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { SnapWyrConfig } from '@snapwyr/core';
2
+ export { LogEntry, SnapWyrConfig } from '@snapwyr/core';
3
+
4
+ interface KoaRequest {
5
+ method: string;
6
+ url: string;
7
+ body?: unknown;
8
+ }
9
+ interface KoaContext {
10
+ method: string;
11
+ url: string;
12
+ status: number;
13
+ request: KoaRequest;
14
+ body?: unknown;
15
+ set: (field: string, val: string) => void;
16
+ }
17
+ type KoaNext = () => Promise<void>;
18
+ type KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;
19
+ declare function snapwyr(config?: SnapWyrConfig): KoaMiddleware;
20
+
21
+ export { snapwyr };
package/dist/koa.js ADDED
@@ -0,0 +1,185 @@
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/koa.ts
21
+ var koa_exports = {};
22
+ __export(koa_exports, {
23
+ snapwyr: () => snapwyr
24
+ });
25
+ module.exports = __toCommonJS(koa_exports);
26
+ var import_core2 = require("@snapwyr/core");
27
+
28
+ // src/utils.ts
29
+ var import_core = require("@snapwyr/core");
30
+ function logRequest(params) {
31
+ const { id, method, status, duration, url, startTime, config, error } = params;
32
+ let { requestBody, responseBody } = params;
33
+ if (config.silent) return;
34
+ const showTimestamp = config.showTimestamp !== false;
35
+ const format = config.format || "pretty";
36
+ const slowThreshold = config.slowThreshold ?? 1e3;
37
+ const isSlow = duration >= slowThreshold;
38
+ const requestSize = requestBody ? (0, import_core.getByteSize)(requestBody) : 0;
39
+ const responseSize = responseBody ? (0, import_core.getByteSize)(responseBody) : 0;
40
+ const totalSize = requestSize + responseSize;
41
+ if (config.redact && config.redact.length > 0) {
42
+ if (requestBody)
43
+ requestBody = (0, import_core.redactSensitiveData)(requestBody, config.redact);
44
+ if (responseBody)
45
+ responseBody = (0, import_core.redactSensitiveData)(responseBody, config.redact);
46
+ }
47
+ const logEntry = {
48
+ id,
49
+ timestamp: new Date(startTime).toISOString(),
50
+ method: method.toUpperCase(),
51
+ url,
52
+ status,
53
+ duration,
54
+ slow: isSlow
55
+ };
56
+ if (config.prefix) logEntry.prefix = config.prefix;
57
+ if (error) logEntry.error = error;
58
+ if (requestBody) logEntry.requestBody = requestBody;
59
+ if (responseBody) logEntry.responseBody = responseBody;
60
+ if (config.sizeTracking) {
61
+ logEntry.requestSize = requestSize;
62
+ logEntry.responseSize = responseSize;
63
+ logEntry.totalSize = totalSize;
64
+ }
65
+ try {
66
+ import_core.snapwyr.emit("request", {
67
+ id,
68
+ method: method.toUpperCase(),
69
+ url,
70
+ status,
71
+ duration,
72
+ timestamp: startTime,
73
+ requestBody,
74
+ responseBody,
75
+ error,
76
+ requestSize: config.sizeTracking ? requestSize : void 0,
77
+ responseSize: config.sizeTracking ? responseSize : void 0,
78
+ direction: "incoming"
79
+ });
80
+ } catch {
81
+ }
82
+ if (config.transport) {
83
+ config.transport(logEntry);
84
+ }
85
+ if (format === "json") {
86
+ console.log(JSON.stringify(logEntry));
87
+ return;
88
+ }
89
+ const useEmoji = config.emoji === true;
90
+ let statusEmoji = "";
91
+ if (useEmoji) {
92
+ if (error || status >= 500) statusEmoji = "\u2717 ";
93
+ else if (status >= 400) statusEmoji = "\u26A0 ";
94
+ else if (status >= 300) statusEmoji = "\u21AA ";
95
+ else statusEmoji = "\u2713 ";
96
+ }
97
+ const statusColor = status >= 500 ? "\x1B[31m" : status >= 400 ? "\x1B[33m" : status >= 300 ? "\x1B[36m" : "\x1B[32m";
98
+ const durationColor = isSlow ? "\x1B[31m" : duration < 100 ? "\x1B[32m" : "\x1B[33m";
99
+ const methodColors = {
100
+ GET: "\x1B[34m",
101
+ POST: "\x1B[32m",
102
+ PUT: "\x1B[33m",
103
+ PATCH: "\x1B[35m",
104
+ DELETE: "\x1B[31m"
105
+ };
106
+ const methodColor = methodColors[method] || "";
107
+ const reset = "\x1B[0m";
108
+ const dim = "\x1B[2m";
109
+ const bold = "\x1B[1m";
110
+ const timestamp = showTimestamp ? new Date(startTime).toISOString().slice(11, 23) + " " : "";
111
+ const slowIndicator = isSlow ? ` ${bold}[SLOW]${reset}` : "";
112
+ const requestIdDisplay = config.requestId ? `${dim}[${id}]${reset} ` : "";
113
+ const sizeDisplay = config.sizeTracking ? `${dim}${(0, import_core.formatBytes)(totalSize)}${reset} ` : "";
114
+ const parts = [
115
+ config.prefix ? `${dim}${config.prefix} ${reset}` : "",
116
+ requestIdDisplay,
117
+ `${dim}${timestamp}${reset}`,
118
+ `${methodColor}${method.padEnd(6)}${reset}`,
119
+ `${statusColor}${statusEmoji}${status}${reset}`,
120
+ `${durationColor}${duration}ms${reset}${slowIndicator}`,
121
+ sizeDisplay,
122
+ `${dim}${url}${reset}`
123
+ ].filter(Boolean);
124
+ if (error) parts.push(`
125
+ ${dim}Error: ${error}${reset}`);
126
+ if (requestBody) parts.push(`
127
+ ${dim}Request: ${requestBody}${reset}`);
128
+ if (responseBody) parts.push(`
129
+ ${dim}Response: ${responseBody}${reset}`);
130
+ console.log(parts.join(" "));
131
+ }
132
+
133
+ // src/koa.ts
134
+ function snapwyr(config = {}) {
135
+ return async function(ctx, next) {
136
+ if (config.enabled === false) {
137
+ await next();
138
+ return;
139
+ }
140
+ const id = (0, import_core2.generateRequestId)();
141
+ const startTime = Date.now();
142
+ if (config.requestId)
143
+ try {
144
+ ctx.set("X-Request-ID", id);
145
+ } catch {
146
+ }
147
+ await next();
148
+ const duration = Date.now() - startTime;
149
+ const status = ctx.status || 200;
150
+ if (config.statusCodes && config.statusCodes.length > 0 && !config.statusCodes.includes(status))
151
+ return;
152
+ let requestBody, responseBody;
153
+ if (config.logBody) {
154
+ try {
155
+ if (ctx.request.body) {
156
+ requestBody = typeof ctx.request.body === "string" ? ctx.request.body : JSON.stringify(ctx.request.body);
157
+ if (config.bodySizeLimit)
158
+ requestBody = requestBody.slice(0, config.bodySizeLimit);
159
+ }
160
+ if (ctx.body) {
161
+ responseBody = typeof ctx.body === "string" ? ctx.body : JSON.stringify(ctx.body);
162
+ if (config.bodySizeLimit)
163
+ responseBody = responseBody.slice(0, config.bodySizeLimit);
164
+ }
165
+ } catch {
166
+ }
167
+ }
168
+ logRequest({
169
+ id,
170
+ method: ctx.method,
171
+ status,
172
+ duration,
173
+ url: ctx.url,
174
+ startTime,
175
+ config,
176
+ requestBody: config.logBody ? requestBody : void 0,
177
+ responseBody: config.logBody ? responseBody : void 0
178
+ });
179
+ };
180
+ }
181
+ // Annotate the CommonJS export names for ESM import in node:
182
+ 0 && (module.exports = {
183
+ snapwyr
184
+ });
185
+ //# sourceMappingURL=koa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/koa.ts","../src/utils.ts"],"sourcesContent":["import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport { generateRequestId } from '@snapwyr/core';\nimport { logRequest } from './utils.js';\n\ninterface KoaRequest {\n method: string;\n url: string;\n body?: unknown;\n}\ninterface KoaContext {\n method: string;\n url: string;\n status: number;\n request: KoaRequest;\n body?: unknown;\n set: (field: string, val: string) => void;\n}\ntype KoaNext = () => Promise<void>;\ntype KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;\n\nexport function snapwyr(config: SnapWyrConfig = {}): KoaMiddleware {\n return async function (ctx, next) {\n if (config.enabled === false) {\n await next();\n return;\n }\n\n const id = generateRequestId();\n const startTime = Date.now();\n\n if (config.requestId)\n try {\n ctx.set('X-Request-ID', id);\n } catch {}\n\n await next();\n\n const duration = Date.now() - startTime;\n const status = ctx.status || 200;\n\n if (\n config.statusCodes &&\n config.statusCodes.length > 0 &&\n !config.statusCodes.includes(status)\n )\n return;\n\n let requestBody: string | undefined, responseBody: string | undefined;\n if (config.logBody) {\n try {\n if (ctx.request.body) {\n requestBody =\n typeof ctx.request.body === 'string'\n ? ctx.request.body\n : JSON.stringify(ctx.request.body);\n if (config.bodySizeLimit)\n requestBody = requestBody.slice(0, config.bodySizeLimit);\n }\n if (ctx.body) {\n responseBody =\n typeof ctx.body === 'string' ? ctx.body : JSON.stringify(ctx.body);\n if (config.bodySizeLimit)\n responseBody = responseBody.slice(0, config.bodySizeLimit);\n }\n } catch {}\n }\n\n logRequest({\n id,\n method: ctx.method,\n status,\n duration,\n url: ctx.url,\n startTime,\n config,\n requestBody: config.logBody ? requestBody : undefined,\n responseBody: config.logBody ? responseBody : undefined,\n });\n };\n}\n\nexport type { SnapWyrConfig, LogEntry };\n","import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport {\n snapwyr as coreEmitter,\n getByteSize,\n formatBytes,\n redactSensitiveData,\n} from '@snapwyr/core';\n\nexport interface LogParams {\n id: string;\n method: string;\n status: number;\n duration: number;\n url: string;\n startTime: number;\n config: SnapWyrConfig;\n requestBody?: string;\n responseBody?: string;\n error?: string;\n}\n\nexport function logRequest(params: LogParams): void {\n const { id, method, status, duration, url, startTime, config, error } =\n params;\n let { requestBody, responseBody } = params;\n\n if (config.silent) return;\n\n const showTimestamp = config.showTimestamp !== false;\n const format = config.format || 'pretty';\n const slowThreshold = config.slowThreshold ?? 1000;\n const isSlow = duration >= slowThreshold;\n\n const requestSize = requestBody ? getByteSize(requestBody) : 0;\n const responseSize = responseBody ? getByteSize(responseBody) : 0;\n const totalSize = requestSize + responseSize;\n\n if (config.redact && config.redact.length > 0) {\n if (requestBody)\n requestBody = redactSensitiveData(requestBody, config.redact);\n if (responseBody)\n responseBody = redactSensitiveData(responseBody, config.redact);\n }\n\n const logEntry: LogEntry = {\n id,\n timestamp: new Date(startTime).toISOString(),\n method: method.toUpperCase(),\n url,\n status,\n duration,\n slow: isSlow,\n };\n\n if (config.prefix) logEntry.prefix = config.prefix;\n if (error) logEntry.error = error;\n if (requestBody) logEntry.requestBody = requestBody;\n if (responseBody) logEntry.responseBody = responseBody;\n if (config.sizeTracking) {\n logEntry.requestSize = requestSize;\n logEntry.responseSize = responseSize;\n logEntry.totalSize = totalSize;\n }\n\n try {\n coreEmitter.emit('request', {\n id,\n method: method.toUpperCase(),\n url,\n status,\n duration,\n timestamp: startTime,\n requestBody,\n responseBody,\n error,\n requestSize: config.sizeTracking ? requestSize : undefined,\n responseSize: config.sizeTracking ? responseSize : undefined,\n direction: 'incoming',\n });\n } catch {}\n\n if (config.transport) {\n config.transport(logEntry);\n }\n\n if (format === 'json') {\n console.log(JSON.stringify(logEntry));\n return;\n }\n\n const useEmoji = config.emoji === true;\n let statusEmoji = '';\n if (useEmoji) {\n if (error || status >= 500) statusEmoji = '✗ ';\n else if (status >= 400) statusEmoji = '⚠ ';\n else if (status >= 300) statusEmoji = '↪ ';\n else statusEmoji = '✓ ';\n }\n\n const statusColor =\n status >= 500\n ? '\\x1b[31m'\n : status >= 400\n ? '\\x1b[33m'\n : status >= 300\n ? '\\x1b[36m'\n : '\\x1b[32m';\n const durationColor = isSlow\n ? '\\x1b[31m'\n : duration < 100\n ? '\\x1b[32m'\n : '\\x1b[33m';\n const methodColors: Record<string, string> = {\n GET: '\\x1b[34m',\n POST: '\\x1b[32m',\n PUT: '\\x1b[33m',\n PATCH: '\\x1b[35m',\n DELETE: '\\x1b[31m',\n };\n const methodColor = methodColors[method] || '';\n const reset = '\\x1b[0m';\n const dim = '\\x1b[2m';\n const bold = '\\x1b[1m';\n const timestamp = showTimestamp\n ? new Date(startTime).toISOString().slice(11, 23) + ' '\n : '';\n const slowIndicator = isSlow ? ` ${bold}[SLOW]${reset}` : '';\n const requestIdDisplay = config.requestId ? `${dim}[${id}]${reset} ` : '';\n const sizeDisplay = config.sizeTracking\n ? `${dim}${formatBytes(totalSize)}${reset} `\n : '';\n\n const parts = [\n config.prefix ? `${dim}${config.prefix} ${reset}` : '',\n requestIdDisplay,\n `${dim}${timestamp}${reset}`,\n `${methodColor}${method.padEnd(6)}${reset}`,\n `${statusColor}${statusEmoji}${status}${reset}`,\n `${durationColor}${duration}ms${reset}${slowIndicator}`,\n sizeDisplay,\n `${dim}${url}${reset}`,\n ].filter(Boolean);\n\n if (error) parts.push(`\\n ${dim}Error: ${error}${reset}`);\n if (requestBody) parts.push(`\\n ${dim}Request: ${requestBody}${reset}`);\n if (responseBody) parts.push(`\\n ${dim}Response: ${responseBody}${reset}`);\n\n console.log(parts.join(' '));\n}\n"],"mappings":"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","names":["import_core","coreEmitter"]}
package/dist/koa.mjs ADDED
@@ -0,0 +1,165 @@
1
+ // src/koa.ts
2
+ import { generateRequestId } from "@snapwyr/core";
3
+
4
+ // src/utils.ts
5
+ import {
6
+ snapwyr as coreEmitter,
7
+ getByteSize,
8
+ formatBytes,
9
+ redactSensitiveData
10
+ } from "@snapwyr/core";
11
+ function logRequest(params) {
12
+ const { id, method, status, duration, url, startTime, config, error } = params;
13
+ let { requestBody, responseBody } = params;
14
+ if (config.silent) return;
15
+ const showTimestamp = config.showTimestamp !== false;
16
+ const format = config.format || "pretty";
17
+ const slowThreshold = config.slowThreshold ?? 1e3;
18
+ const isSlow = duration >= slowThreshold;
19
+ const requestSize = requestBody ? getByteSize(requestBody) : 0;
20
+ const responseSize = responseBody ? getByteSize(responseBody) : 0;
21
+ const totalSize = requestSize + responseSize;
22
+ if (config.redact && config.redact.length > 0) {
23
+ if (requestBody)
24
+ requestBody = redactSensitiveData(requestBody, config.redact);
25
+ if (responseBody)
26
+ responseBody = redactSensitiveData(responseBody, config.redact);
27
+ }
28
+ const logEntry = {
29
+ id,
30
+ timestamp: new Date(startTime).toISOString(),
31
+ method: method.toUpperCase(),
32
+ url,
33
+ status,
34
+ duration,
35
+ slow: isSlow
36
+ };
37
+ if (config.prefix) logEntry.prefix = config.prefix;
38
+ if (error) logEntry.error = error;
39
+ if (requestBody) logEntry.requestBody = requestBody;
40
+ if (responseBody) logEntry.responseBody = responseBody;
41
+ if (config.sizeTracking) {
42
+ logEntry.requestSize = requestSize;
43
+ logEntry.responseSize = responseSize;
44
+ logEntry.totalSize = totalSize;
45
+ }
46
+ try {
47
+ coreEmitter.emit("request", {
48
+ id,
49
+ method: method.toUpperCase(),
50
+ url,
51
+ status,
52
+ duration,
53
+ timestamp: startTime,
54
+ requestBody,
55
+ responseBody,
56
+ error,
57
+ requestSize: config.sizeTracking ? requestSize : void 0,
58
+ responseSize: config.sizeTracking ? responseSize : void 0,
59
+ direction: "incoming"
60
+ });
61
+ } catch {
62
+ }
63
+ if (config.transport) {
64
+ config.transport(logEntry);
65
+ }
66
+ if (format === "json") {
67
+ console.log(JSON.stringify(logEntry));
68
+ return;
69
+ }
70
+ const useEmoji = config.emoji === true;
71
+ let statusEmoji = "";
72
+ if (useEmoji) {
73
+ if (error || status >= 500) statusEmoji = "\u2717 ";
74
+ else if (status >= 400) statusEmoji = "\u26A0 ";
75
+ else if (status >= 300) statusEmoji = "\u21AA ";
76
+ else statusEmoji = "\u2713 ";
77
+ }
78
+ const statusColor = status >= 500 ? "\x1B[31m" : status >= 400 ? "\x1B[33m" : status >= 300 ? "\x1B[36m" : "\x1B[32m";
79
+ const durationColor = isSlow ? "\x1B[31m" : duration < 100 ? "\x1B[32m" : "\x1B[33m";
80
+ const methodColors = {
81
+ GET: "\x1B[34m",
82
+ POST: "\x1B[32m",
83
+ PUT: "\x1B[33m",
84
+ PATCH: "\x1B[35m",
85
+ DELETE: "\x1B[31m"
86
+ };
87
+ const methodColor = methodColors[method] || "";
88
+ const reset = "\x1B[0m";
89
+ const dim = "\x1B[2m";
90
+ const bold = "\x1B[1m";
91
+ const timestamp = showTimestamp ? new Date(startTime).toISOString().slice(11, 23) + " " : "";
92
+ const slowIndicator = isSlow ? ` ${bold}[SLOW]${reset}` : "";
93
+ const requestIdDisplay = config.requestId ? `${dim}[${id}]${reset} ` : "";
94
+ const sizeDisplay = config.sizeTracking ? `${dim}${formatBytes(totalSize)}${reset} ` : "";
95
+ const parts = [
96
+ config.prefix ? `${dim}${config.prefix} ${reset}` : "",
97
+ requestIdDisplay,
98
+ `${dim}${timestamp}${reset}`,
99
+ `${methodColor}${method.padEnd(6)}${reset}`,
100
+ `${statusColor}${statusEmoji}${status}${reset}`,
101
+ `${durationColor}${duration}ms${reset}${slowIndicator}`,
102
+ sizeDisplay,
103
+ `${dim}${url}${reset}`
104
+ ].filter(Boolean);
105
+ if (error) parts.push(`
106
+ ${dim}Error: ${error}${reset}`);
107
+ if (requestBody) parts.push(`
108
+ ${dim}Request: ${requestBody}${reset}`);
109
+ if (responseBody) parts.push(`
110
+ ${dim}Response: ${responseBody}${reset}`);
111
+ console.log(parts.join(" "));
112
+ }
113
+
114
+ // src/koa.ts
115
+ function snapwyr(config = {}) {
116
+ return async function(ctx, next) {
117
+ if (config.enabled === false) {
118
+ await next();
119
+ return;
120
+ }
121
+ const id = generateRequestId();
122
+ const startTime = Date.now();
123
+ if (config.requestId)
124
+ try {
125
+ ctx.set("X-Request-ID", id);
126
+ } catch {
127
+ }
128
+ await next();
129
+ const duration = Date.now() - startTime;
130
+ const status = ctx.status || 200;
131
+ if (config.statusCodes && config.statusCodes.length > 0 && !config.statusCodes.includes(status))
132
+ return;
133
+ let requestBody, responseBody;
134
+ if (config.logBody) {
135
+ try {
136
+ if (ctx.request.body) {
137
+ requestBody = typeof ctx.request.body === "string" ? ctx.request.body : JSON.stringify(ctx.request.body);
138
+ if (config.bodySizeLimit)
139
+ requestBody = requestBody.slice(0, config.bodySizeLimit);
140
+ }
141
+ if (ctx.body) {
142
+ responseBody = typeof ctx.body === "string" ? ctx.body : JSON.stringify(ctx.body);
143
+ if (config.bodySizeLimit)
144
+ responseBody = responseBody.slice(0, config.bodySizeLimit);
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ logRequest({
150
+ id,
151
+ method: ctx.method,
152
+ status,
153
+ duration,
154
+ url: ctx.url,
155
+ startTime,
156
+ config,
157
+ requestBody: config.logBody ? requestBody : void 0,
158
+ responseBody: config.logBody ? responseBody : void 0
159
+ });
160
+ };
161
+ }
162
+ export {
163
+ snapwyr
164
+ };
165
+ //# sourceMappingURL=koa.mjs.map