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.
- package/README.md +275 -0
- package/dist/cjs/adapters/adapter.interface.js +9 -0
- package/dist/cjs/adapters/adapter.interface.js.map +1 -0
- package/dist/cjs/adapters/mongo.adapter.js +188 -0
- package/dist/cjs/adapters/mongo.adapter.js.map +1 -0
- package/dist/cjs/adapters/mysql.adapter.js +243 -0
- package/dist/cjs/adapters/mysql.adapter.js.map +1 -0
- package/dist/cjs/adapters/pg.adapter.js +334 -0
- package/dist/cjs/adapters/pg.adapter.js.map +1 -0
- package/dist/cjs/capture.js +310 -0
- package/dist/cjs/capture.js.map +1 -0
- package/dist/cjs/config.js +122 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/dashboard/api.js +173 -0
- package/dist/cjs/dashboard/api.js.map +1 -0
- package/dist/cjs/dashboard/router.js +96 -0
- package/dist/cjs/dashboard/router.js.map +1 -0
- package/dist/cjs/index.js +49 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/masker.js +73 -0
- package/dist/cjs/masker.js.map +1 -0
- package/dist/cjs/middleware.js +198 -0
- package/dist/cjs/middleware.js.map +1 -0
- package/dist/cjs/queue.js +114 -0
- package/dist/cjs/queue.js.map +1 -0
- package/dist/cjs/retention.js +64 -0
- package/dist/cjs/retention.js.map +1 -0
- package/dist/cjs/types.js +9 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/dashboard/assets/index-C0TqFHk6.css +1 -0
- package/dist/dashboard/assets/index-MCuAZo4Q.js +67 -0
- package/dist/dashboard/index.html +13 -0
- package/dist/esm/adapters/adapter.interface.js +8 -0
- package/dist/esm/adapters/adapter.interface.js.map +1 -0
- package/dist/esm/adapters/mongo.adapter.js +184 -0
- package/dist/esm/adapters/mongo.adapter.js.map +1 -0
- package/dist/esm/adapters/mysql.adapter.js +236 -0
- package/dist/esm/adapters/mysql.adapter.js.map +1 -0
- package/dist/esm/adapters/pg.adapter.js +330 -0
- package/dist/esm/adapters/pg.adapter.js.map +1 -0
- package/dist/esm/capture.js +304 -0
- package/dist/esm/capture.js.map +1 -0
- package/dist/esm/config.js +117 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/dashboard/api.js +168 -0
- package/dist/esm/dashboard/api.js.map +1 -0
- package/dist/esm/dashboard/router.js +90 -0
- package/dist/esm/dashboard/router.js.map +1 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/masker.js +70 -0
- package/dist/esm/masker.js.map +1 -0
- package/dist/esm/middleware.js +193 -0
- package/dist/esm/middleware.js.map +1 -0
- package/dist/esm/queue.js +110 -0
- package/dist/esm/queue.js.map +1 -0
- package/dist/esm/retention.js +60 -0
- package/dist/esm/retention.js.map +1 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/adapters/adapter.interface.d.ts +7 -0
- package/dist/types/adapters/mongo.adapter.d.ts +25 -0
- package/dist/types/adapters/mysql.adapter.d.ts +24 -0
- package/dist/types/adapters/pg.adapter.d.ts +29 -0
- package/dist/types/capture.d.ts +88 -0
- package/dist/types/config.d.ts +38 -0
- package/dist/types/dashboard/api.d.ts +49 -0
- package/dist/types/dashboard/router.d.ts +28 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/masker.d.ts +15 -0
- package/dist/types/middleware.d.ts +67 -0
- package/dist/types/queue.d.ts +49 -0
- package/dist/types/retention.d.ts +30 -0
- package/dist/types/types.d.ts +101 -0
- 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":";AAAA;;;;;;;GAOG;;;AAIH,MAAa,UAAU;IAKrB,YACmB,OAAuB,EACvB,YAAoB,EAAE,EACtB,kBAA0B,IAAI,EAC9B,cAAsB,KAAK;QAH3B,YAAO,GAAP,OAAO,CAAgB;QACvB,cAAS,GAAT,SAAS,CAAa;QACtB,oBAAe,GAAf,eAAe,CAAe;QAC9B,gBAAW,GAAX,WAAW,CAAgB;QARtC,UAAK,GAAoB,EAAE,CAAC;QAC5B,eAAU,GAA0B,IAAI,CAAC;QACzC,eAAU,GAAY,KAAK,CAAC;QAQlC,8DAA8D;QAC9D,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YACjC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAEzB,wEAAwE;QACxE,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,MAAqB;QAC3B,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+CAA+C,IAAI,CAAC,WAAW,KAAK;gBAClE,cAAc,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,eAAe,CAC3D,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAExB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,KAAK;QACjB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,uEAAuE;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,OAAO,GACX,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qDAAqD,KAAK,CAAC,MAAM,wBAAwB;gBACvF,UAAU,OAAO,IAAI,CACxB,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,CAAC,YAAoB,KAAK;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAExC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sCAAsC,SAAS,CAAC,MAAM,oCAAoC;oBACxF,SAAS;yBACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC;yBACrD,IAAI,CAAC,IAAI,CAAC;oBACb,IAAI,CACP,CAAC;gBACF,OAAO;YACT,CAAC;YAED,uEAAuE;YACvE,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9D,SAAS;YACX,CAAC;YAED,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC7B,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;IACH,CAAC;CACF;AAxHD,gCAwHC"}
|
|
@@ -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"}
|