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,96 @@
1
+ "use strict";
2
+ /**
3
+ * RequestScope — Dashboard router.
4
+ *
5
+ * Provides an Express router with:
6
+ * - Optional BasicAuth middleware
7
+ * - API routes for querying records
8
+ * - Static file serving for the React SPA
9
+ * - SPA fallback for client-side routing
10
+ *
11
+ * Requirements: 8.1, 8.2, 8.3, 8.4
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createDashboardRouter = createDashboardRouter;
18
+ const express_1 = require("express");
19
+ const path_1 = __importDefault(require("path"));
20
+ const api_1 = require("./api");
21
+ // ---------------------------------------------------------------------------
22
+ // BasicAuth middleware
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Creates a BasicAuth middleware if auth config is provided.
26
+ *
27
+ * Checks the Authorization header for Basic auth credentials.
28
+ * If credentials are missing or don't match, responds with 401 and
29
+ * WWW-Authenticate header.
30
+ */
31
+ function createBasicAuthMiddleware(auth) {
32
+ if (!auth) {
33
+ return (_req, _res, next) => next();
34
+ }
35
+ const { username, password } = auth;
36
+ const expectedAuth = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
37
+ return (req, res, next) => {
38
+ const authHeader = req.headers.authorization;
39
+ if (!authHeader || authHeader !== expectedAuth) {
40
+ res.setHeader('WWW-Authenticate', 'Basic realm="RequestScope"');
41
+ res.status(401).json({ error: 'Unauthorized' });
42
+ return;
43
+ }
44
+ next();
45
+ };
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Dashboard router factory
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * Creates and returns an Express router for the RequestScope dashboard.
52
+ *
53
+ * The router includes:
54
+ * - BasicAuth middleware (if auth config is provided)
55
+ * - GET /api/records — List records with filtering and pagination
56
+ * - GET /api/records/:id — Get a single record by ID
57
+ * - Static file serving for the React SPA
58
+ * - SPA fallback for client-side routing
59
+ *
60
+ * @param config - Optional dashboard configuration
61
+ * @param adapter - Storage adapter instance (must be set on the app)
62
+ * @returns Express Router
63
+ */
64
+ function createDashboardRouter(config, adapter) {
65
+ const router = (0, express_1.Router)();
66
+ // Install BasicAuth middleware if auth config is present
67
+ const authMiddleware = createBasicAuthMiddleware(config?.auth);
68
+ router.use(authMiddleware);
69
+ // Set the adapter on the app for API handlers to access
70
+ router.use((req, res, next) => {
71
+ if (adapter) {
72
+ req.app.set('requestscopeAdapter', adapter);
73
+ }
74
+ next();
75
+ });
76
+ // Mount API routes
77
+ router.get('/api/records', api_1.getRecords);
78
+ router.get('/api/records/:id', api_1.getRecordById);
79
+ router.delete('/api/records', api_1.deleteAllRecords);
80
+ // Serve static files from the dashboard bundle
81
+ // The dashboard is built to dist/dashboard
82
+ // Resolve from project root regardless of where this file is
83
+ const dashboardPath = path_1.default.resolve(process.cwd(), 'dist/dashboard');
84
+ // Serve static files from the built dashboard
85
+ router.use((0, express_1.static)(dashboardPath));
86
+ // Explicitly serve index.html for the root path
87
+ router.get('/', (req, res) => {
88
+ res.sendFile(path_1.default.join(dashboardPath, 'index.html'));
89
+ });
90
+ // SPA fallback for client-side routing (must be after static files)
91
+ router.get('*', (req, res) => {
92
+ res.sendFile(path_1.default.join(dashboardPath, 'index.html'));
93
+ });
94
+ return router;
95
+ }
96
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/dashboard/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * RequestScope — Public API entry point.
4
+ *
5
+ * Exports:
6
+ * - Default export: requestscope(config) middleware factory
7
+ * - requestscope.dashboard: dashboard router factory
8
+ * - All public TypeScript interfaces
9
+ *
10
+ * Requirements: 1.1, 12.1, 12.2, 12.3, 12.4
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.requestscopeMiddleware = exports.errorHandler = exports.setup = void 0;
14
+ exports.dashboard = dashboard;
15
+ const middleware_1 = require("./middleware");
16
+ Object.defineProperty(exports, "errorHandler", { enumerable: true, get: function () { return middleware_1.errorHandler; } });
17
+ Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return middleware_1.setup; } });
18
+ const router_1 = require("./dashboard/router");
19
+ // ---------------------------------------------------------------------------
20
+ // Main middleware factory
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Creates and returns an Express middleware that captures HTTP requests.
24
+ *
25
+ * @param config - RequestScope configuration object
26
+ * @returns Express RequestHandler middleware
27
+ */
28
+ exports.default = middleware_1.requestscope;
29
+ // ---------------------------------------------------------------------------
30
+ // Dashboard router factory
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Creates and returns an Express router for the RequestScope dashboard.
34
+ *
35
+ * @param config - Optional dashboard configuration
36
+ * @param adapter - Optional storage adapter instance
37
+ * @returns Express Router
38
+ */
39
+ function dashboard(config, adapter) {
40
+ return (0, router_1.createDashboardRouter)(config, adapter);
41
+ }
42
+ // Attach dashboard as a named property on the default export for convenience
43
+ middleware_1.requestscope.dashboard = dashboard;
44
+ // ---------------------------------------------------------------------------
45
+ // Named exports for convenience
46
+ // ---------------------------------------------------------------------------
47
+ var middleware_2 = require("./middleware");
48
+ Object.defineProperty(exports, "requestscopeMiddleware", { enumerable: true, get: function () { return middleware_2.requestscope; } });
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAsCH,8BAEC;AArCD,6CAA2F;AAoDlF,6FApDwC,yBAAY,OAoDxC;AANZ,sFA9CsD,kBAAK,OA8CtD;AA7Cd,+CAA2D;AAW3D,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;;GAKG;AACH,kBAAe,yBAAsB,CAAC;AAEtC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,MAAwB,EAAE,OAAwB;IAC1E,OAAO,IAAA,8BAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,6EAA6E;AAC5E,yBAA8B,CAAC,SAAS,GAAG,SAAS,CAAC;AA4BtD,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,2CAAsE;AAA7D,oHAAA,YAAY,OAA0B"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * masker.ts — Recursive sensitive field masking
4
+ *
5
+ * Provides a pure function that recursively replaces sensitive field values
6
+ * with "******" at any nesting depth.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.maskObject = maskObject;
10
+ /**
11
+ * Checks if a value is a plain object (not an Array, Date, or class instance).
12
+ *
13
+ * @param val - The value to check
14
+ * @returns True if val is a plain object
15
+ */
16
+ function isPlainObject(val) {
17
+ if (val === null || typeof val !== 'object') {
18
+ return false;
19
+ }
20
+ // Reject arrays
21
+ if (Array.isArray(val)) {
22
+ return false;
23
+ }
24
+ // Check if it's a plain object (created by {} or Object.create(null))
25
+ // using Object.prototype.toString
26
+ const proto = Object.getPrototypeOf(val);
27
+ return proto === null || proto === Object.prototype;
28
+ }
29
+ /**
30
+ * Recursively masks sensitive fields in an object.
31
+ *
32
+ * @param obj - The object to mask
33
+ * @param sensitiveFields - Set of field names to mask (stored in lowercase)
34
+ * @param depth - Current recursion depth (default: 0)
35
+ * @returns A new object with sensitive fields replaced by "******"
36
+ */
37
+ function maskObject(obj, sensitiveFields, depth = 0) {
38
+ // Prevent infinite recursion
39
+ if (depth > 10) {
40
+ return obj;
41
+ }
42
+ const result = {};
43
+ for (const key in obj) {
44
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
45
+ continue;
46
+ }
47
+ const value = obj[key];
48
+ // Check if this key is sensitive (case-insensitive comparison)
49
+ if (sensitiveFields.has(key.toLowerCase())) {
50
+ result[key] = '******';
51
+ continue;
52
+ }
53
+ // Recurse into plain objects
54
+ if (isPlainObject(value)) {
55
+ result[key] = maskObject(value, sensitiveFields, depth + 1);
56
+ continue;
57
+ }
58
+ // Map over arrays: recurse on plain objects, pass primitives through
59
+ if (Array.isArray(value)) {
60
+ result[key] = value.map((element) => {
61
+ if (isPlainObject(element)) {
62
+ return maskObject(element, sensitiveFields, depth + 1);
63
+ }
64
+ return element;
65
+ });
66
+ continue;
67
+ }
68
+ // Pass through all other values (primitives, dates, etc.)
69
+ result[key] = value;
70
+ }
71
+ return result;
72
+ }
73
+ //# sourceMappingURL=masker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masker.js","sourceRoot":"","sources":["../../src/masker.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAgCH,gCA+CC;AA7ED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gBAAgB;IAChB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sEAAsE;IACtE,kCAAkC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,UAAU,CACxB,GAA4B,EAC5B,eAA4B,EAC5B,QAAgB,CAAC;IAEjB,6BAA6B;IAC7B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YACpD,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvB,+DAA+D;QAC/D,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;YACvB,SAAS;QACX,CAAC;QAED,6BAA6B;QAC7B,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC5D,SAAS;QACX,CAAC;QAED,qEAAqE;QACrE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAClC,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,OAAO,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBACzD,CAAC;gBACD,OAAO,OAAO,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,0DAA0D;QAC1D,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /**
3
+ * RequestScope — Core middleware factory.
4
+ *
5
+ * Provides:
6
+ * - requestscope(config) — Express middleware factory that validates config,
7
+ * instantiates the appropriate storage adapter, creates AsyncQueue and
8
+ * RetentionScheduler, and returns a request handler.
9
+ * - errorHandler — 4-arity error middleware that attaches error data to
10
+ * the request for capture by the finish handler.
11
+ *
12
+ * Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.2, 4.1, 4.2, 4.3, 5.1
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.requestscope = requestscope;
16
+ exports.setup = setup;
17
+ exports.errorHandler = errorHandler;
18
+ const config_1 = require("./config");
19
+ const mongo_adapter_1 = require("./adapters/mongo.adapter");
20
+ const mysql_adapter_1 = require("./adapters/mysql.adapter");
21
+ const pg_adapter_1 = require("./adapters/pg.adapter");
22
+ const queue_1 = require("./queue");
23
+ const retention_1 = require("./retention");
24
+ const capture_1 = require("./capture");
25
+ const router_1 = require("./dashboard/router");
26
+ // ---------------------------------------------------------------------------
27
+ // Factory function
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Creates and returns an Express middleware that captures HTTP requests.
31
+ *
32
+ * Validation and initialization:
33
+ * 1. Calls `validateConfig()`, `applyDefaults()`, and `buildSensitiveFieldSet()`
34
+ * synchronously; throws on any validation error.
35
+ * 2. Instantiates the correct `StorageAdapter` based on `storage.type` (unless adapter is provided).
36
+ * 3. Calls `adapter.initialize()` asynchronously; logs errors to `stderr`
37
+ * without throwing (middleware continues to function even if storage fails).
38
+ * 4. Creates `AsyncQueue` and `RetentionScheduler`; starts both.
39
+ * 5. Attaches queue `drain()` to `process.on('SIGTERM')` and
40
+ * `process.on('SIGINT')` for graceful shutdown.
41
+ *
42
+ * Request handling:
43
+ * - Checks `ignore` list; skips capture and calls `next()` if matched.
44
+ * - Buffers request body via `bufferRequestBody()`.
45
+ * - Wraps response via `wrapResponse()` to accumulate response chunks.
46
+ * - On response finish, builds `RequestRecord` and enqueues it.
47
+ *
48
+ * @param config - RequestScope configuration object
49
+ * @param adapter - Optional storage adapter instance (for sharing with dashboard)
50
+ * @returns Express RequestHandler middleware
51
+ */
52
+ function requestscope(config, adapter) {
53
+ // 1. Validate and apply defaults synchronously
54
+ (0, config_1.validateConfig)(config);
55
+ (0, config_1.applyDefaults)(config);
56
+ const sensitiveFields = (0, config_1.buildSensitiveFieldSet)(config);
57
+ // 2. Instantiate the appropriate storage adapter (if not provided)
58
+ if (!adapter) {
59
+ switch (config.storage.type) {
60
+ case 'mongodb':
61
+ adapter = new mongo_adapter_1.MongoAdapter(config.storage);
62
+ break;
63
+ case 'mysql':
64
+ adapter = new mysql_adapter_1.MySQLAdapter(config.storage);
65
+ break;
66
+ case 'postgresql':
67
+ adapter = new pg_adapter_1.PgAdapter(config.storage);
68
+ break;
69
+ default:
70
+ // This should never happen due to validateConfig, but TypeScript needs it
71
+ throw new Error(`Unsupported storage type: ${config.storage.type}`);
72
+ }
73
+ }
74
+ // 3. Initialize adapter asynchronously (log errors, don't throw)
75
+ void adapter.initialize().catch((err) => {
76
+ const message = err instanceof Error ? err.message : String(err);
77
+ process.stderr.write(`[RequestScope] Failed to initialize storage adapter: ${message}\n`);
78
+ });
79
+ // 4. Create and start AsyncQueue and RetentionScheduler
80
+ const queue = new queue_1.AsyncQueue(adapter);
81
+ const retentionDays = config.retentionDays ?? 30;
82
+ const retentionScheduler = new retention_1.RetentionScheduler(adapter, retentionDays);
83
+ retentionScheduler.start();
84
+ // 5. Attach graceful shutdown handlers
85
+ const shutdown = async () => {
86
+ retentionScheduler.stop();
87
+ await queue.drain(10000);
88
+ queue.destroy();
89
+ };
90
+ process.on('SIGTERM', () => {
91
+ void shutdown();
92
+ });
93
+ process.on('SIGINT', () => {
94
+ void shutdown();
95
+ });
96
+ // 6. Return the request handler middleware
97
+ return async (req, res, next) => {
98
+ // Check ignore list
99
+ const ignoreList = config.ignore ?? [];
100
+ const url = req.originalUrl || req.url || '/';
101
+ // Always ignore dashboard routes
102
+ if (url.startsWith('/requestscope')) {
103
+ next();
104
+ return;
105
+ }
106
+ if (ignoreList.some((pattern) => url === pattern || url.startsWith(pattern))) {
107
+ next();
108
+ return;
109
+ }
110
+ try {
111
+ // Buffer request body
112
+ const { body: requestBody, bodySize: requestBodySize, clientIp } = await (0, capture_1.bufferRequestBody)(req);
113
+ // Wrap response to capture response data
114
+ (0, capture_1.wrapResponse)(res, req, requestBody, requestBodySize, clientIp, sensitiveFields, (record) => queue.enqueue(record));
115
+ }
116
+ catch (err) {
117
+ // If capture fails, log and continue without disrupting the request
118
+ const message = err instanceof Error ? err.message : String(err);
119
+ process.stderr.write(`[RequestScope] Capture error: ${message}\n`);
120
+ }
121
+ next();
122
+ };
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Setup function
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Sets up RequestScope middleware and automatically mounts the dashboard at /requestscope.
129
+ *
130
+ * This function:
131
+ * 1. Creates the storage adapter
132
+ * 2. Sets up the request capture middleware
133
+ * 3. Automatically mounts the dashboard at /requestscope
134
+ *
135
+ * @param app - Express application instance
136
+ * @param config - RequestScope configuration object
137
+ */
138
+ function setup(app, config) {
139
+ // Validate and apply defaults
140
+ (0, config_1.validateConfig)(config);
141
+ (0, config_1.applyDefaults)(config);
142
+ // Instantiate the appropriate storage adapter
143
+ let adapter;
144
+ switch (config.storage.type) {
145
+ case 'mongodb':
146
+ adapter = new mongo_adapter_1.MongoAdapter(config.storage);
147
+ break;
148
+ case 'mysql':
149
+ adapter = new mysql_adapter_1.MySQLAdapter(config.storage);
150
+ break;
151
+ case 'postgresql':
152
+ adapter = new pg_adapter_1.PgAdapter(config.storage);
153
+ break;
154
+ default:
155
+ throw new Error(`Unsupported storage type: ${config.storage.type}`);
156
+ }
157
+ // Initialize adapter asynchronously
158
+ void adapter.initialize().catch((err) => {
159
+ const message = err instanceof Error ? err.message : String(err);
160
+ process.stderr.write(`[RequestScope] Failed to initialize storage adapter: ${message}\n`);
161
+ });
162
+ // Set adapter on app for dashboard API to use
163
+ app.set('requestscopeAdapter', adapter);
164
+ // Use the middleware with the shared adapter
165
+ app.use(requestscope(config, adapter));
166
+ // Mount dashboard at /requestscope
167
+ app.use('/requestscope', (0, router_1.createDashboardRouter)(config.dashboard, adapter));
168
+ }
169
+ // ---------------------------------------------------------------------------
170
+ // Error middleware
171
+ // ---------------------------------------------------------------------------
172
+ /**
173
+ * 4-arity error middleware that attaches error data to the request.
174
+ *
175
+ * The error data (message, stack, statusCode) is attached via a Symbol-keyed
176
+ * property so the response finish handler can include it in the RequestRecord.
177
+ *
178
+ * Usage:
179
+ * app.use(requestscope(config));
180
+ * app.use(errorHandler);
181
+ * // ... route handlers that may throw
182
+ *
183
+ * @param err - Error object
184
+ * @param req - Express Request
185
+ * @param res - Express Response
186
+ * @param next - Express NextFunction
187
+ */
188
+ function errorHandler(err, req, res, next) {
189
+ const errorData = {
190
+ message: err.message,
191
+ stack: err.stack || '',
192
+ statusCode: err.statusCode || 500,
193
+ };
194
+ req[capture_1.ERROR_DATA_SYMBOL] = errorData;
195
+ // Pass error to next error handler in the chain
196
+ next(err);
197
+ }
198
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * AsyncQueue — In-process batch write queue.
4
+ *
5
+ * Decouples the hot-path enqueue (synchronous, ≤1 ms) from database writes,
6
+ * which happen asynchronously in configurable batches. Fault isolation is
7
+ * total: adapter failures are caught, logged to stderr, and discarded so
8
+ * they never propagate to the caller.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.AsyncQueue = void 0;
12
+ class AsyncQueue {
13
+ constructor(adapter, batchSize = 50, flushIntervalMs = 5000, maxCapacity = 10000) {
14
+ this.adapter = adapter;
15
+ this.batchSize = batchSize;
16
+ this.flushIntervalMs = flushIntervalMs;
17
+ this.maxCapacity = maxCapacity;
18
+ this.items = [];
19
+ this.flushTimer = null;
20
+ this.isFlushing = false;
21
+ // Start the periodic flush timer immediately on construction.
22
+ this.flushTimer = setInterval(() => {
23
+ void this.flush();
24
+ }, this.flushIntervalMs);
25
+ // Allow the Node.js process to exit even if this timer is still active.
26
+ if (this.flushTimer.unref) {
27
+ this.flushTimer.unref();
28
+ }
29
+ }
30
+ /**
31
+ * Synchronously adds a record to the queue.
32
+ *
33
+ * If the queue is at capacity, the record is discarded and a warning is
34
+ * written to stderr. If the queue has reached `batchSize` after this push,
35
+ * a flush is triggered immediately (fire-and-forget).
36
+ */
37
+ enqueue(record) {
38
+ if (this.items.length >= this.maxCapacity) {
39
+ process.stderr.write(`[RequestScope] Queue capacity exceeded (max ${this.maxCapacity}). ` +
40
+ `Record for ${record.method} ${record.url} discarded.\n`);
41
+ return;
42
+ }
43
+ this.items.push(record);
44
+ if (this.items.length >= this.batchSize) {
45
+ void this.flush();
46
+ }
47
+ }
48
+ /**
49
+ * Flushes up to `batchSize` items from the front of the queue.
50
+ *
51
+ * A guard (`isFlushing`) prevents concurrent flushes. The splice is
52
+ * performed atomically before the async adapter call so no records are
53
+ * lost even if the adapter rejects. On failure the batch is discarded
54
+ * and the error is written to stderr.
55
+ */
56
+ async flush() {
57
+ if (this.isFlushing || this.items.length === 0) {
58
+ return;
59
+ }
60
+ this.isFlushing = true;
61
+ // Atomically remove up to batchSize items from the front of the array.
62
+ const batch = this.items.splice(0, this.batchSize);
63
+ try {
64
+ await this.adapter.insert(batch);
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ process.stderr.write(`[RequestScope] adapter.insert() failed — batch of ${batch.length} record(s) discarded. ` +
69
+ `Error: ${message}\n`);
70
+ }
71
+ finally {
72
+ this.isFlushing = false;
73
+ }
74
+ }
75
+ /**
76
+ * Flushes all remaining items in the queue, waiting at most `timeoutMs`
77
+ * milliseconds. Any items that could not be flushed before the timeout
78
+ * are logged to stderr.
79
+ *
80
+ * Used during graceful shutdown (SIGTERM / SIGINT).
81
+ */
82
+ async drain(timeoutMs = 10000) {
83
+ const deadline = Date.now() + timeoutMs;
84
+ while (this.items.length > 0) {
85
+ if (Date.now() >= deadline) {
86
+ const remaining = this.items.splice(0);
87
+ process.stderr.write(`[RequestScope] drain() timed out — ${remaining.length} record(s) could not be flushed:\n` +
88
+ remaining
89
+ .map((r) => ` ${r.method} ${r.url} @ ${r.timestamp}`)
90
+ .join('\n') +
91
+ '\n');
92
+ return;
93
+ }
94
+ // Wait for any in-progress flush to complete before starting the next.
95
+ if (this.isFlushing) {
96
+ await new Promise((resolve) => setTimeout(resolve, 10));
97
+ continue;
98
+ }
99
+ await this.flush();
100
+ }
101
+ }
102
+ /**
103
+ * Clears the periodic flush timer. Call this to allow the Node.js process
104
+ * to exit cleanly, or when the queue is no longer needed.
105
+ */
106
+ destroy() {
107
+ if (this.flushTimer !== null) {
108
+ clearInterval(this.flushTimer);
109
+ this.flushTimer = null;
110
+ }
111
+ }
112
+ }
113
+ exports.AsyncQueue = AsyncQueue;
114
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * RetentionScheduler — automatically purges records older than the configured
4
+ * retention window by calling `adapter.deleteOlderThan()` once per day.
5
+ *
6
+ * Requirements: 7.1, 7.2, 7.4, 7.5
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.RetentionScheduler = void 0;
10
+ const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000; // 86_400_000 ms
11
+ class RetentionScheduler {
12
+ constructor(adapter, retentionDays) {
13
+ this.adapter = adapter;
14
+ this.retentionDays = retentionDays;
15
+ this.intervalHandle = null;
16
+ }
17
+ /**
18
+ * Runs the retention job immediately, then schedules it to repeat every 24 h.
19
+ * Calling `start()` more than once has no additional effect until `stop()` is
20
+ * called first (the previous interval is replaced by the new one).
21
+ */
22
+ start() {
23
+ // Run an initial sweep right away so old records are not kept waiting up
24
+ // to 24 h after the server first starts.
25
+ void this.runOnce();
26
+ // Replace any previously-running interval.
27
+ if (this.intervalHandle !== null) {
28
+ clearInterval(this.intervalHandle);
29
+ }
30
+ this.intervalHandle = setInterval(() => {
31
+ void this.runOnce();
32
+ }, TWENTY_FOUR_HOURS_MS);
33
+ }
34
+ /**
35
+ * Stops the scheduled retention job. Inflight `runOnce()` calls are allowed
36
+ * to complete naturally because they are fire-and-forget.
37
+ */
38
+ stop() {
39
+ if (this.intervalHandle !== null) {
40
+ clearInterval(this.intervalHandle);
41
+ this.intervalHandle = null;
42
+ }
43
+ }
44
+ /**
45
+ * Computes the cutoff date from `retentionDays` and asks the adapter to
46
+ * delete every record older than that date. Any error is logged to `stderr`
47
+ * but never rethrown so the scheduler stays alive for the next tick.
48
+ */
49
+ async runOnce() {
50
+ const cutoff = new Date(Date.now() - this.retentionDays * 86400000);
51
+ try {
52
+ const deleted = await this.adapter.deleteOlderThan(cutoff);
53
+ if (deleted > 0) {
54
+ process.stderr.write(`[RequestScope] RetentionScheduler: deleted ${deleted} record(s) older than ${cutoff.toISOString()}\n`);
55
+ }
56
+ }
57
+ catch (err) {
58
+ const message = err instanceof Error ? err.message : String(err);
59
+ process.stderr.write(`[RequestScope] RetentionScheduler error: ${message}\n`);
60
+ }
61
+ }
62
+ }
63
+ exports.RetentionScheduler = RetentionScheduler;
64
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/retention.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAIH,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,gBAAgB;AAElE,MAAa,kBAAkB;IAG7B,YACmB,OAAuB,EACvB,aAAqB;QADrB,YAAO,GAAP,OAAO,CAAgB;QACvB,kBAAa,GAAb,aAAa,CAAQ;QAJhC,mBAAc,GAA0C,IAAI,CAAC;IAKlE,CAAC;IAEJ;;;;OAIG;IACH,KAAK;QACH,yEAAyE;QACzE,yCAAyC;QACzC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QAEpB,2CAA2C;QAC3C,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,OAAO;QACnB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,QAAU,CAAC,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;YAC3D,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8CAA8C,OAAO,yBAAyB,MAAM,CAAC,WAAW,EAAE,IAAI,CACvG,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4CAA4C,OAAO,IAAI,CACxD,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA5DD,gDA4DC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * RequestScope — All exported TypeScript interfaces.
4
+ *
5
+ * This file is the single source of truth for every public type in the library.
6
+ * All interfaces are written to compile under `strict: true` with zero errors.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}