request-scope-api 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 (75) hide show
  1. package/README.md +275 -0
  2. package/dist/cjs/adapters/adapter.interface.js +9 -0
  3. package/dist/cjs/adapters/adapter.interface.js.map +1 -0
  4. package/dist/cjs/adapters/mongo.adapter.js +188 -0
  5. package/dist/cjs/adapters/mongo.adapter.js.map +1 -0
  6. package/dist/cjs/adapters/mysql.adapter.js +243 -0
  7. package/dist/cjs/adapters/mysql.adapter.js.map +1 -0
  8. package/dist/cjs/adapters/pg.adapter.js +334 -0
  9. package/dist/cjs/adapters/pg.adapter.js.map +1 -0
  10. package/dist/cjs/capture.js +310 -0
  11. package/dist/cjs/capture.js.map +1 -0
  12. package/dist/cjs/config.js +122 -0
  13. package/dist/cjs/config.js.map +1 -0
  14. package/dist/cjs/dashboard/api.js +173 -0
  15. package/dist/cjs/dashboard/api.js.map +1 -0
  16. package/dist/cjs/dashboard/router.js +96 -0
  17. package/dist/cjs/dashboard/router.js.map +1 -0
  18. package/dist/cjs/index.js +49 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/masker.js +73 -0
  21. package/dist/cjs/masker.js.map +1 -0
  22. package/dist/cjs/middleware.js +198 -0
  23. package/dist/cjs/middleware.js.map +1 -0
  24. package/dist/cjs/queue.js +114 -0
  25. package/dist/cjs/queue.js.map +1 -0
  26. package/dist/cjs/retention.js +64 -0
  27. package/dist/cjs/retention.js.map +1 -0
  28. package/dist/cjs/types.js +9 -0
  29. package/dist/cjs/types.js.map +1 -0
  30. package/dist/dashboard/assets/index-C0TqFHk6.css +1 -0
  31. package/dist/dashboard/assets/index-MCuAZo4Q.js +67 -0
  32. package/dist/dashboard/index.html +13 -0
  33. package/dist/esm/adapters/adapter.interface.js +8 -0
  34. package/dist/esm/adapters/adapter.interface.js.map +1 -0
  35. package/dist/esm/adapters/mongo.adapter.js +184 -0
  36. package/dist/esm/adapters/mongo.adapter.js.map +1 -0
  37. package/dist/esm/adapters/mysql.adapter.js +236 -0
  38. package/dist/esm/adapters/mysql.adapter.js.map +1 -0
  39. package/dist/esm/adapters/pg.adapter.js +330 -0
  40. package/dist/esm/adapters/pg.adapter.js.map +1 -0
  41. package/dist/esm/capture.js +304 -0
  42. package/dist/esm/capture.js.map +1 -0
  43. package/dist/esm/config.js +117 -0
  44. package/dist/esm/config.js.map +1 -0
  45. package/dist/esm/dashboard/api.js +168 -0
  46. package/dist/esm/dashboard/api.js.map +1 -0
  47. package/dist/esm/dashboard/router.js +90 -0
  48. package/dist/esm/dashboard/router.js.map +1 -0
  49. package/dist/esm/index.js +50 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/masker.js +70 -0
  52. package/dist/esm/masker.js.map +1 -0
  53. package/dist/esm/middleware.js +193 -0
  54. package/dist/esm/middleware.js.map +1 -0
  55. package/dist/esm/queue.js +110 -0
  56. package/dist/esm/queue.js.map +1 -0
  57. package/dist/esm/retention.js +60 -0
  58. package/dist/esm/retention.js.map +1 -0
  59. package/dist/esm/types.js +8 -0
  60. package/dist/esm/types.js.map +1 -0
  61. package/dist/types/adapters/adapter.interface.d.ts +7 -0
  62. package/dist/types/adapters/mongo.adapter.d.ts +25 -0
  63. package/dist/types/adapters/mysql.adapter.d.ts +24 -0
  64. package/dist/types/adapters/pg.adapter.d.ts +29 -0
  65. package/dist/types/capture.d.ts +88 -0
  66. package/dist/types/config.d.ts +38 -0
  67. package/dist/types/dashboard/api.d.ts +49 -0
  68. package/dist/types/dashboard/router.d.ts +28 -0
  69. package/dist/types/index.d.ts +31 -0
  70. package/dist/types/masker.d.ts +15 -0
  71. package/dist/types/middleware.d.ts +67 -0
  72. package/dist/types/queue.d.ts +49 -0
  73. package/dist/types/retention.d.ts +30 -0
  74. package/dist/types/types.d.ts +101 -0
  75. package/package.json +48 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * PgAdapter — StorageAdapter implementation for PostgreSQL using pg.
3
+ *
4
+ * Compiles under `strict: true` with zero errors.
5
+ * Requirements: 1.6, 1.7, 6.1, 6.4, 6.5, 7.4
6
+ */
7
+ import { Pool } from 'pg';
8
+ // ---------------------------------------------------------------------------
9
+ // Column name mapping: JS camelCase → SQL snake_case
10
+ // ---------------------------------------------------------------------------
11
+ const SORT_COLUMN_MAP = {
12
+ timestamp: 'timestamp',
13
+ responseTime: 'response_time',
14
+ statusCode: 'status_code',
15
+ };
16
+ // ---------------------------------------------------------------------------
17
+ // DDL
18
+ // ---------------------------------------------------------------------------
19
+ const DDL_TABLE = `
20
+ CREATE TABLE IF NOT EXISTS requestscope_records (
21
+ id VARCHAR(36) NOT NULL PRIMARY KEY,
22
+ method VARCHAR(10) NOT NULL,
23
+ url TEXT NOT NULL,
24
+ route TEXT,
25
+ query_params JSONB NOT NULL,
26
+ path_params JSONB NOT NULL,
27
+ request_headers JSONB NOT NULL,
28
+ request_body TEXT NOT NULL,
29
+ request_body_size INTEGER NOT NULL,
30
+ client_ip VARCHAR(45) NOT NULL,
31
+ user_agent TEXT NOT NULL,
32
+ status_code SMALLINT NOT NULL,
33
+ response_headers JSONB NOT NULL,
34
+ response_body TEXT NOT NULL,
35
+ response_body_size INTEGER NOT NULL,
36
+ response_time REAL NOT NULL,
37
+ error_message TEXT,
38
+ error_stack TEXT,
39
+ error_status_code SMALLINT,
40
+ timestamp VARCHAR(30) NOT NULL
41
+ )
42
+ `.trim();
43
+ const DDL_IDX_TIMESTAMP = `CREATE INDEX IF NOT EXISTS idx_rs_timestamp ON requestscope_records (timestamp DESC)`;
44
+ const DDL_IDX_STATUS = `CREATE INDEX IF NOT EXISTS idx_rs_status ON requestscope_records (status_code)`;
45
+ const DDL_IDX_METHOD = `CREATE INDEX IF NOT EXISTS idx_rs_method ON requestscope_records (method)`;
46
+ // ---------------------------------------------------------------------------
47
+ // Helpers
48
+ // ---------------------------------------------------------------------------
49
+ /**
50
+ * Ensure a JSONB column value (which pg already parses to an object) is
51
+ * returned as `Record<string, string>`. Falls back to `{}` on any error.
52
+ */
53
+ function parseJsonb(value) {
54
+ if (value === null || value === undefined)
55
+ return {};
56
+ if (typeof value === 'object')
57
+ return value;
58
+ if (typeof value === 'string') {
59
+ try {
60
+ return JSON.parse(value);
61
+ }
62
+ catch {
63
+ return {};
64
+ }
65
+ }
66
+ return {};
67
+ }
68
+ /** Map a database row back to a `RequestRecord`. */
69
+ function rowToRecord(row) {
70
+ return {
71
+ id: row.id,
72
+ method: row.method,
73
+ url: row.url,
74
+ route: row.route,
75
+ queryParams: parseJsonb(row.query_params),
76
+ pathParams: parseJsonb(row.path_params),
77
+ requestHeaders: parseJsonb(row.request_headers),
78
+ requestBody: row.request_body,
79
+ requestBodySize: Number(row.request_body_size),
80
+ clientIp: row.client_ip,
81
+ userAgent: row.user_agent,
82
+ statusCode: Number(row.status_code),
83
+ responseHeaders: parseJsonb(row.response_headers),
84
+ responseBody: row.response_body,
85
+ responseBodySize: Number(row.response_body_size),
86
+ responseTime: Number(row.response_time),
87
+ errorMessage: row.error_message,
88
+ errorStack: row.error_stack,
89
+ errorStatusCode: row.error_status_code !== null ? Number(row.error_status_code) : null,
90
+ timestamp: row.timestamp,
91
+ };
92
+ }
93
+ /** Resolve the `statusCodeGroup` filter to a `[min, max)` pair. */
94
+ function statusGroupRange(group) {
95
+ const base = parseInt(group[0], 10) * 100;
96
+ return [base, base + 100];
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // PgAdapter
100
+ // ---------------------------------------------------------------------------
101
+ export class PgAdapter {
102
+ constructor(config) {
103
+ this.pool = null;
104
+ this.config = config;
105
+ }
106
+ // -------------------------------------------------------------------------
107
+ // initialize
108
+ // -------------------------------------------------------------------------
109
+ async initialize() {
110
+ const poolConfig = {
111
+ host: this.config.host ?? 'localhost',
112
+ port: this.config.port ?? 5432,
113
+ database: this.config.database,
114
+ user: this.config.username,
115
+ password: this.config.password,
116
+ max: this.config.poolSize ?? 5,
117
+ ssl: this.config.ssl ? { rejectUnauthorized: true } : undefined,
118
+ };
119
+ this.pool = new Pool(poolConfig);
120
+ const client = await this.pool.connect();
121
+ try {
122
+ await client.query(DDL_TABLE);
123
+ await client.query(DDL_IDX_TIMESTAMP);
124
+ await client.query(DDL_IDX_STATUS);
125
+ await client.query(DDL_IDX_METHOD);
126
+ }
127
+ finally {
128
+ client.release();
129
+ }
130
+ }
131
+ // -------------------------------------------------------------------------
132
+ // insert
133
+ // -------------------------------------------------------------------------
134
+ /**
135
+ * Batch-inserts records using unnest arrays for efficient multi-row inserts.
136
+ *
137
+ * INSERT INTO requestscope_records (col1, col2, ...)
138
+ * SELECT * FROM unnest($1::varchar[], $2::text[], ...)
139
+ */
140
+ async insert(records) {
141
+ if (records.length === 0)
142
+ return;
143
+ const pool = this.getPool();
144
+ // Collect per-column arrays
145
+ const ids = [];
146
+ const methods = [];
147
+ const urls = [];
148
+ const routes = [];
149
+ const queryParams = [];
150
+ const pathParams = [];
151
+ const requestHeaders = [];
152
+ const requestBodies = [];
153
+ const requestBodySizes = [];
154
+ const clientIps = [];
155
+ const userAgents = [];
156
+ const statusCodes = [];
157
+ const responseHeaders = [];
158
+ const responseBodies = [];
159
+ const responseBodySizes = [];
160
+ const responseTimes = [];
161
+ const errorMessages = [];
162
+ const errorStacks = [];
163
+ const errorStatusCodes = [];
164
+ const timestamps = [];
165
+ for (const r of records) {
166
+ ids.push(r.id);
167
+ methods.push(r.method);
168
+ urls.push(r.url);
169
+ routes.push(r.route);
170
+ queryParams.push(JSON.stringify(r.queryParams));
171
+ pathParams.push(JSON.stringify(r.pathParams));
172
+ requestHeaders.push(JSON.stringify(r.requestHeaders));
173
+ requestBodies.push(r.requestBody);
174
+ requestBodySizes.push(r.requestBodySize);
175
+ clientIps.push(r.clientIp);
176
+ userAgents.push(r.userAgent);
177
+ statusCodes.push(r.statusCode);
178
+ responseHeaders.push(JSON.stringify(r.responseHeaders));
179
+ responseBodies.push(r.responseBody);
180
+ responseBodySizes.push(r.responseBodySize);
181
+ responseTimes.push(r.responseTime);
182
+ errorMessages.push(r.errorMessage);
183
+ errorStacks.push(r.errorStack);
184
+ errorStatusCodes.push(r.errorStatusCode);
185
+ timestamps.push(r.timestamp);
186
+ }
187
+ const sql = `
188
+ INSERT INTO requestscope_records (
189
+ id, method, url, route,
190
+ query_params, path_params, request_headers,
191
+ request_body, request_body_size,
192
+ client_ip, user_agent,
193
+ status_code, response_headers,
194
+ response_body, response_body_size,
195
+ response_time,
196
+ error_message, error_stack, error_status_code,
197
+ timestamp
198
+ )
199
+ SELECT * FROM unnest(
200
+ $1::varchar[],
201
+ $2::varchar[],
202
+ $3::text[],
203
+ $4::text[],
204
+ $5::jsonb[],
205
+ $6::jsonb[],
206
+ $7::jsonb[],
207
+ $8::text[],
208
+ $9::integer[],
209
+ $10::varchar[],
210
+ $11::text[],
211
+ $12::smallint[],
212
+ $13::jsonb[],
213
+ $14::text[],
214
+ $15::integer[],
215
+ $16::real[],
216
+ $17::text[],
217
+ $18::text[],
218
+ $19::smallint[],
219
+ $20::varchar[]
220
+ )
221
+ `.trim();
222
+ await pool.query(sql, [
223
+ ids,
224
+ methods,
225
+ urls,
226
+ routes,
227
+ queryParams,
228
+ pathParams,
229
+ requestHeaders,
230
+ requestBodies,
231
+ requestBodySizes,
232
+ clientIps,
233
+ userAgents,
234
+ statusCodes,
235
+ responseHeaders,
236
+ responseBodies,
237
+ responseBodySizes,
238
+ responseTimes,
239
+ errorMessages,
240
+ errorStacks,
241
+ errorStatusCodes,
242
+ timestamps,
243
+ ]);
244
+ }
245
+ // -------------------------------------------------------------------------
246
+ // query
247
+ // -------------------------------------------------------------------------
248
+ async query(filters, pagination) {
249
+ const pool = this.getPool();
250
+ const conditions = [];
251
+ const params = [];
252
+ let paramIdx = 1;
253
+ // Helper to add a parameter and return its $N placeholder
254
+ const addParam = (value) => {
255
+ params.push(value);
256
+ return `$${paramIdx++}`;
257
+ };
258
+ // id — exact match
259
+ if (filters.id !== undefined) {
260
+ conditions.push(`id = ${addParam(filters.id)}`);
261
+ }
262
+ // search — url ILIKE $N
263
+ if (filters.search !== undefined) {
264
+ conditions.push(`url ILIKE ${addParam(`%${filters.search}%`)}`);
265
+ }
266
+ // startDate — timestamp >= $N
267
+ if (filters.startDate !== undefined) {
268
+ conditions.push(`timestamp >= ${addParam(filters.startDate)}`);
269
+ }
270
+ // endDate — timestamp <= $N
271
+ if (filters.endDate !== undefined) {
272
+ conditions.push(`timestamp <= ${addParam(filters.endDate)}`);
273
+ }
274
+ // statusCode (exact, takes precedence over group)
275
+ if (filters.statusCode !== undefined) {
276
+ conditions.push(`status_code = ${addParam(filters.statusCode)}`);
277
+ }
278
+ else if (filters.statusCodeGroup !== undefined) {
279
+ const [min, max] = statusGroupRange(filters.statusCodeGroup);
280
+ conditions.push(`status_code >= ${addParam(min)} AND status_code < ${addParam(max)}`);
281
+ }
282
+ // method — case-insensitive
283
+ if (filters.method !== undefined) {
284
+ conditions.push(`LOWER(method) = LOWER(${addParam(filters.method)})`);
285
+ }
286
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
287
+ // Sort
288
+ const sortColRaw = filters.sortBy ?? 'timestamp';
289
+ const sortCol = SORT_COLUMN_MAP[sortColRaw] ?? 'timestamp';
290
+ const sortDir = (filters.sortOrder ?? 'desc').toUpperCase() === 'ASC' ? 'ASC' : 'DESC';
291
+ const orderClause = `ORDER BY ${sortCol} ${sortDir}`;
292
+ // COUNT query (reuses same params)
293
+ const countSql = `SELECT COUNT(*) AS total FROM requestscope_records ${whereClause}`;
294
+ const countResult = await pool.query(countSql, params);
295
+ const total = Number(countResult.rows[0]?.total ?? 0);
296
+ // Page slice — add LIMIT and OFFSET as the next params
297
+ const { page, pageSize } = pagination;
298
+ const offset = (Math.max(1, page) - 1) * pageSize;
299
+ const limitPlaceholder = addParam(pageSize);
300
+ const offsetPlaceholder = addParam(offset);
301
+ const dataSql = `
302
+ SELECT * FROM requestscope_records
303
+ ${whereClause}
304
+ ${orderClause}
305
+ LIMIT ${limitPlaceholder} OFFSET ${offsetPlaceholder}
306
+ `.trim();
307
+ const dataResult = await pool.query(dataSql, params);
308
+ const records = dataResult.rows.map(rowToRecord);
309
+ return { records, total };
310
+ }
311
+ // -------------------------------------------------------------------------
312
+ // deleteOlderThan
313
+ // -------------------------------------------------------------------------
314
+ async deleteOlderThan(date) {
315
+ const pool = this.getPool();
316
+ const iso = date.toISOString();
317
+ const result = await pool.query('DELETE FROM requestscope_records WHERE timestamp < $1', [iso]);
318
+ return result.rowCount ?? 0;
319
+ }
320
+ // -------------------------------------------------------------------------
321
+ // Private helpers
322
+ // -------------------------------------------------------------------------
323
+ getPool() {
324
+ if (this.pool === null) {
325
+ throw new Error('PgAdapter: call initialize() before using the adapter');
326
+ }
327
+ return this.pool;
328
+ }
329
+ }
330
+ //# sourceMappingURL=pg.adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.adapter.js","sourceRoot":"","sources":["../../../src/adapters/pg.adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * RequestScope — Capture layer, request and response sides.
3
+ *
4
+ * Provides:
5
+ * - parseClientIp(req) — extract client IP from headers/socket
6
+ * - bufferRequestBody(req) — consume request stream, buffer ≤50 KB,
7
+ * attempt JSON / URL-encoded parse, return
8
+ * body string + original byte size + client IP.
9
+ * - wrapResponse(res) — monkey-patch res.write/res.end to accumulate
10
+ * response chunks, cap at 100 KB, return wrapper
11
+ * with finish event handler that builds RequestRecord.
12
+ *
13
+ * Uses Node.js built-ins only (no external deps).
14
+ * Compiles under strict: true.
15
+ */
16
+ import { randomUUID } from 'node:crypto';
17
+ import { maskObject } from './masker';
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+ /** Maximum bytes to retain from a request body before truncation. */
22
+ const MAX_BODY_BYTES = 50000;
23
+ /** Maximum bytes to retain from a response body before truncation. */
24
+ const MAX_RESPONSE_BODY_BYTES = 100000;
25
+ /** Suffix appended to a body that was truncated. */
26
+ const TRUNCATED_SUFFIX = '[truncated]';
27
+ // ---------------------------------------------------------------------------
28
+ // Client IP extraction
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Extracts the client IP address from the request.
32
+ *
33
+ * Prefers the first entry of the `X-Forwarded-For` header (proxy-aware),
34
+ * falling back to `req.socket.remoteAddress`. Returns `"0.0.0.0"` when
35
+ * neither is available.
36
+ *
37
+ * @param req Node.js IncomingMessage (or Express Request)
38
+ */
39
+ export function parseClientIp(req) {
40
+ const xff = req.headers['x-forwarded-for'];
41
+ if (xff) {
42
+ const raw = Array.isArray(xff) ? xff[0] : xff;
43
+ // X-Forwarded-For may be comma-separated: "client, proxy1, proxy2"
44
+ const first = raw.split(',')[0].trim();
45
+ if (first) {
46
+ return first;
47
+ }
48
+ }
49
+ return req.socket?.remoteAddress ?? '0.0.0.0';
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // URL-encoded body parsing helper
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Attempts to parse `raw` as a URL-encoded query string.
56
+ *
57
+ * Returns a JSON string of the key-value pairs on success, or `null` when
58
+ * the string contains no parseable key-value pairs.
59
+ */
60
+ function tryParseUrlEncoded(raw) {
61
+ // A URL-encoded body must contain at least one '=' (key=value separator).
62
+ // Without this guard, URLSearchParams treats any bare string as a key with
63
+ // an empty value, producing false positives for plain text / XML bodies.
64
+ if (!raw.includes('=')) {
65
+ return null;
66
+ }
67
+ try {
68
+ const params = new URLSearchParams(raw);
69
+ const obj = {};
70
+ params.forEach((value, key) => {
71
+ obj[key] = value;
72
+ });
73
+ // Only treat as URL-encoded if we got at least one key-value pair.
74
+ if (Object.keys(obj).length === 0) {
75
+ return null;
76
+ }
77
+ return JSON.stringify(obj);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Request body buffering
85
+ // ---------------------------------------------------------------------------
86
+ /**
87
+ * Consumes the `req` stream and buffers up to {@link MAX_BODY_BYTES} bytes.
88
+ *
89
+ * Behaviour:
90
+ * 1. Collects `Buffer` chunks from `data` events; tracks total byte length.
91
+ * 2. On `end`: concatenates chunks, slices to MAX_BODY_BYTES; appends
92
+ * `"[truncated]"` when the original size exceeded the limit.
93
+ * 3. Attempts `JSON.parse` → stores normalised JSON string.
94
+ * 4. On JSON failure, attempts `URLSearchParams` parse → stores JSON map.
95
+ * 5. On both failures, stores the raw string as-is.
96
+ * 6. On stream `error`, resolves with `{ body: '', bodySize: 0, clientIp }`.
97
+ *
98
+ * @param req Node.js IncomingMessage (or Express Request)
99
+ * @returns Resolved promise with body string, original byte size, and IP.
100
+ */
101
+ export async function bufferRequestBody(req) {
102
+ const clientIp = parseClientIp(req);
103
+ return new Promise((resolve) => {
104
+ const chunks = [];
105
+ let totalBytes = 0;
106
+ req.on('data', (chunk) => {
107
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
108
+ totalBytes += buf.byteLength;
109
+ chunks.push(buf);
110
+ });
111
+ req.on('end', () => {
112
+ // Concatenate all chunks into one buffer, then slice to the limit.
113
+ const full = Buffer.concat(chunks);
114
+ const truncated = totalBytes > MAX_BODY_BYTES;
115
+ const kept = truncated ? full.subarray(0, MAX_BODY_BYTES) : full;
116
+ let rawString = kept.toString('utf8');
117
+ if (truncated) {
118
+ rawString += TRUNCATED_SUFFIX;
119
+ }
120
+ // Try JSON first.
121
+ try {
122
+ const parsed = JSON.parse(rawString.replace(TRUNCATED_SUFFIX, ''));
123
+ // Store as normalised JSON string (handles whitespace variation).
124
+ const base = JSON.stringify(parsed);
125
+ resolve({
126
+ body: truncated ? base + TRUNCATED_SUFFIX : base,
127
+ bodySize: totalBytes,
128
+ clientIp,
129
+ });
130
+ return;
131
+ }
132
+ catch {
133
+ // JSON parse failed — try URL-encoded next (only on untruncated or
134
+ // the raw portion; URLSearchParams on a truncated string may be
135
+ // structurally invalid, but we try on the raw slice).
136
+ }
137
+ // Try URL-encoded.
138
+ const rawForUrlParse = truncated
139
+ ? full.subarray(0, MAX_BODY_BYTES).toString('utf8')
140
+ : rawString;
141
+ const urlEncoded = tryParseUrlEncoded(rawForUrlParse);
142
+ if (urlEncoded !== null) {
143
+ resolve({
144
+ body: truncated ? urlEncoded + TRUNCATED_SUFFIX : urlEncoded,
145
+ bodySize: totalBytes,
146
+ clientIp,
147
+ });
148
+ return;
149
+ }
150
+ // Fall back to raw string (possibly truncated).
151
+ resolve({ body: rawString, bodySize: totalBytes, clientIp });
152
+ });
153
+ req.on('error', () => {
154
+ resolve({ body: '', bodySize: 0, clientIp });
155
+ });
156
+ });
157
+ }
158
+ // ---------------------------------------------------------------------------
159
+ // Response body wrapping
160
+ // ---------------------------------------------------------------------------
161
+ /**
162
+ * Symbol used to attach error data to the request object for the finish handler.
163
+ */
164
+ export const ERROR_DATA_SYMBOL = Symbol('requestscope_error_data');
165
+ /**
166
+ * Wraps a ServerResponse to accumulate response chunks.
167
+ *
168
+ * Monkey-patches `res.write` and `res.end` to accumulate response body chunks.
169
+ * Caps the buffer at MAX_RESPONSE_BODY_BYTES; beyond that stores first
170
+ * MAX_RESPONSE_BODY_BYTES bytes + "[truncated]". Records responseBodySize as
171
+ * the original Content-Length or accumulated size.
172
+ *
173
+ * On the `finish` event, builds a RequestRecord with:
174
+ * - UUIDv4 id
175
+ * - process.hrtime-based responseTime
176
+ * - maskObject() applied to request headers and request body
177
+ * - maskObject() applied to response headers
178
+ * - Enqueues the record synchronously via the provided callback
179
+ *
180
+ * If wrapping fails (stream/binary response), sets responseBody = "",
181
+ * reads Content-Length header as responseBodySize, and calls original write/end
182
+ * without disrupting the response.
183
+ *
184
+ * @param res - Node.js ServerResponse (or Express Response)
185
+ * @param req - Node.js IncomingMessage (or Express Request)
186
+ * @param requestBody - Buffered request body string
187
+ * @param requestBodySize - Original request body byte size
188
+ * @param clientIp - Client IP address
189
+ * @param sensitiveFields - Set of sensitive field names to mask
190
+ * @param enqueueRecord - Callback to enqueue the RequestRecord synchronously
191
+ * @returns The wrapped response (same object, with patched methods)
192
+ */
193
+ export function wrapResponse(res, req, requestBody, requestBodySize, clientIp, sensitiveFields, enqueueRecord) {
194
+ const chunks = [];
195
+ let totalBytes = 0;
196
+ const startTime = process.hrtime.bigint();
197
+ // Store original methods
198
+ const originalWrite = res.write;
199
+ const originalEnd = res.end;
200
+ // Helper to accumulate chunks
201
+ const accumulateChunk = (chunk, encoding) => {
202
+ try {
203
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
204
+ totalBytes += buf.byteLength;
205
+ if (totalBytes <= MAX_RESPONSE_BODY_BYTES) {
206
+ chunks.push(buf);
207
+ }
208
+ }
209
+ catch (err) {
210
+ // Ignore accumulation errors
211
+ }
212
+ };
213
+ // Patch res.write
214
+ res.write = function (chunk, encoding, cb) {
215
+ accumulateChunk(chunk, encoding);
216
+ return originalWrite.call(this, chunk, encoding, cb);
217
+ };
218
+ // Patch res.end
219
+ res.end = function (chunk, encoding, cb) {
220
+ if (chunk !== undefined) {
221
+ accumulateChunk(chunk, encoding);
222
+ }
223
+ originalEnd.call(this, chunk, encoding, cb);
224
+ };
225
+ // Patch Express-specific methods that might bypass res.write/res.end
226
+ const expressRes = res;
227
+ if (expressRes.send) {
228
+ const originalSend = expressRes.send;
229
+ expressRes.send = function (body) {
230
+ // Capture the body before sending
231
+ if (body !== undefined) {
232
+ const str = typeof body === 'string' ? body : JSON.stringify(body);
233
+ accumulateChunk(str, 'utf8');
234
+ }
235
+ return originalSend.call(this, body);
236
+ };
237
+ }
238
+ if (expressRes.json) {
239
+ const originalJson = expressRes.json;
240
+ expressRes.json = function (obj) {
241
+ const str = JSON.stringify(obj);
242
+ accumulateChunk(str, 'utf8');
243
+ return originalJson.call(this, obj);
244
+ };
245
+ }
246
+ // Listen for finish event to build the record
247
+ res.on('finish', () => {
248
+ const endTime = process.hrtime.bigint();
249
+ const responseTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds
250
+ // Build response body
251
+ let responseBody = '';
252
+ let responseBodySize = totalBytes;
253
+ if (chunks.length > 0) {
254
+ const full = Buffer.concat(chunks);
255
+ const truncated = totalBytes > MAX_RESPONSE_BODY_BYTES;
256
+ const kept = truncated ? full.subarray(0, MAX_RESPONSE_BODY_BYTES) : full;
257
+ responseBody = kept.toString('utf8');
258
+ if (truncated) {
259
+ responseBody += TRUNCATED_SUFFIX;
260
+ }
261
+ }
262
+ else {
263
+ // No chunks accumulated - try to get size from Content-Length header
264
+ const contentLength = res.getHeader('Content-Length');
265
+ if (typeof contentLength === 'number') {
266
+ responseBodySize = contentLength;
267
+ }
268
+ else if (typeof contentLength === 'string') {
269
+ responseBodySize = parseInt(contentLength, 10) || 0;
270
+ }
271
+ // Log when no chunks are accumulated for debugging
272
+ process.stderr.write(`[RequestScope] No response chunks accumulated for ${req.method} ${req.url}\n`);
273
+ }
274
+ // Get error data from request if present
275
+ const errorData = req[ERROR_DATA_SYMBOL];
276
+ // Build request record
277
+ const record = {
278
+ id: randomUUID(),
279
+ method: req.method || 'GET',
280
+ url: req.url || '/',
281
+ route: null, // Will be set by Express if available
282
+ queryParams: {}, // Will be populated by Express
283
+ pathParams: {}, // Will be populated by Express
284
+ requestHeaders: maskObject(Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v ?? ''])), sensitiveFields),
285
+ requestBody,
286
+ requestBodySize,
287
+ clientIp,
288
+ userAgent: req.headers['user-agent'] || '',
289
+ statusCode: res.statusCode,
290
+ responseHeaders: maskObject(Object.fromEntries(Object.entries(res.getHeaders()).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v ?? ''])), sensitiveFields),
291
+ responseBody,
292
+ responseBodySize,
293
+ responseTime,
294
+ errorMessage: errorData?.message ?? null,
295
+ errorStack: errorData?.stack ?? null,
296
+ errorStatusCode: errorData?.statusCode ?? null,
297
+ timestamp: new Date().toISOString(),
298
+ };
299
+ // Enqueue the record synchronously
300
+ enqueueRecord(record);
301
+ });
302
+ return res;
303
+ }
304
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/capture.ts"],"names":[],"mappings":"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"}