request-scope-api 1.0.22 → 1.0.24
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 +37 -0
- package/dist/cjs/config.js +12 -5
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/index.js +27 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/middleware.js +29 -35
- package/dist/cjs/middleware.js.map +1 -1
- package/dist/cjs/retention.js +4 -0
- package/dist/cjs/retention.js.map +1 -1
- package/dist/cjs/shutdown-manager.js +120 -0
- package/dist/cjs/shutdown-manager.js.map +1 -0
- package/dist/esm/config.js +12 -5
- package/dist/esm/index.js +26 -6
- package/dist/esm/middleware.js +29 -35
- package/dist/esm/retention.js +4 -0
- package/dist/esm/shutdown-manager.js +116 -0
- package/dist/types/index.d.ts +19 -1
- package/dist/types/shutdown-manager.d.ts +49 -0
- package/dist/types/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ import { setup } from 'request-scope-api';
|
|
|
21
21
|
const app = express();
|
|
22
22
|
|
|
23
23
|
setup(app, {
|
|
24
|
+
mode: 'development', // or 'production' to disable tracking
|
|
24
25
|
storage: {
|
|
25
26
|
type: 'mongodb',
|
|
26
27
|
uri: 'mongodb://localhost:27017/myapp'
|
|
@@ -39,6 +40,7 @@ import { setup } from 'request-scope-api';
|
|
|
39
40
|
const app = express();
|
|
40
41
|
|
|
41
42
|
setup(app, {
|
|
43
|
+
mode: 'development', // or 'production' to disable tracking
|
|
42
44
|
storage: {
|
|
43
45
|
type: 'mysql',
|
|
44
46
|
host: 'localhost',
|
|
@@ -64,6 +66,7 @@ import { setup } from 'request-scope-api';
|
|
|
64
66
|
const app = express();
|
|
65
67
|
|
|
66
68
|
setup(app, {
|
|
69
|
+
mode: 'development', // or 'production' to disable tracking
|
|
67
70
|
storage: {
|
|
68
71
|
type: 'postgresql',
|
|
69
72
|
host: 'localhost',
|
|
@@ -88,6 +91,40 @@ app.listen(3000);
|
|
|
88
91
|
- **Performance Metrics**: Records response time and body sizes
|
|
89
92
|
- **Dashboard UI**: Web interface for browsing and filtering requests
|
|
90
93
|
- **Basic Auth**: Optional authentication for dashboard access
|
|
94
|
+
- **Production Mode**: Completely disables tracking in production for security and performance
|
|
95
|
+
|
|
96
|
+
## Mode Configuration
|
|
97
|
+
|
|
98
|
+
The package supports two modes: `development` and `production`.
|
|
99
|
+
|
|
100
|
+
### Development Mode (Default)
|
|
101
|
+
- Enables all request tracking, logging, and data collection
|
|
102
|
+
- Runs retention scheduler and background processing
|
|
103
|
+
- Dashboard is accessible
|
|
104
|
+
- Ideal for local development and testing
|
|
105
|
+
|
|
106
|
+
### Production Mode
|
|
107
|
+
- Completely disables request tracking and data collection
|
|
108
|
+
- No database connections are established
|
|
109
|
+
- No background services or retention jobs run
|
|
110
|
+
- Dashboard is disabled
|
|
111
|
+
- Acts as a no-op middleware for security and performance
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
// Development mode (default)
|
|
115
|
+
setup(app, {
|
|
116
|
+
mode: 'development',
|
|
117
|
+
storage: { /* ... */ }
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Production mode - tracking disabled
|
|
121
|
+
setup(app, {
|
|
122
|
+
mode: 'production',
|
|
123
|
+
storage: { /* ... */ }
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Note:** In production mode, the package does not establish any database connections and has zero performance impact on your application.
|
|
91
128
|
|
|
92
129
|
## Sensitive Field Masking
|
|
93
130
|
|
package/dist/cjs/config.js
CHANGED
|
@@ -44,6 +44,9 @@ const DEFAULT_SENSITIVE_FIELDS = [
|
|
|
44
44
|
* storage.ssl → false
|
|
45
45
|
*/
|
|
46
46
|
function applyDefaults(config) {
|
|
47
|
+
if (config.mode === undefined) {
|
|
48
|
+
config.mode = 'development';
|
|
49
|
+
}
|
|
47
50
|
if (config.retentionDays === undefined) {
|
|
48
51
|
config.retentionDays = 30;
|
|
49
52
|
}
|
|
@@ -75,25 +78,29 @@ function applyDefaults(config) {
|
|
|
75
78
|
* 4. `retentionDays` (when provided) must be a positive integer in [1, 365].
|
|
76
79
|
*/
|
|
77
80
|
function validateConfig(config) {
|
|
78
|
-
const { storage, retentionDays } = config;
|
|
79
|
-
// 1. Validate
|
|
81
|
+
const { storage, retentionDays, mode } = config;
|
|
82
|
+
// 1. Validate mode
|
|
83
|
+
if (mode !== undefined && mode !== 'development' && mode !== 'production') {
|
|
84
|
+
throw new Error(`Invalid mode: '${mode}'. Supported values are: development, production`);
|
|
85
|
+
}
|
|
86
|
+
// 2. Validate storage.type
|
|
80
87
|
if (!SUPPORTED_STORAGE_TYPES.includes(storage.type)) {
|
|
81
88
|
throw new Error(`Invalid storage type: '${storage.type}'. Supported values are: mongodb, mysql, postgresql`);
|
|
82
89
|
}
|
|
83
|
-
//
|
|
90
|
+
// 3. MongoDB: uri is required
|
|
84
91
|
if (storage.type === 'mongodb') {
|
|
85
92
|
if (!storage.uri) {
|
|
86
93
|
throw new Error("storage.uri is required when storage.type is 'mongodb'");
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
|
-
//
|
|
96
|
+
// 4. MySQL / PostgreSQL: all SQL required fields must be present
|
|
90
97
|
if (storage.type === 'mysql' || storage.type === 'postgresql') {
|
|
91
98
|
const missing = SQL_REQUIRED_FIELDS.filter((field) => storage[field] === undefined || storage[field] === null);
|
|
92
99
|
if (missing.length > 0) {
|
|
93
100
|
throw new Error(`Missing required storage fields: ${missing.join(', ')}`);
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
|
-
//
|
|
103
|
+
// 5. retentionDays: must be a positive integer in [1, 365] when provided
|
|
97
104
|
if (retentionDays !== undefined) {
|
|
98
105
|
const isValidInteger = typeof retentionDays === 'number' &&
|
|
99
106
|
Number.isInteger(retentionDays) &&
|
package/dist/cjs/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AA0CH,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -15,11 +15,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.requestscopeMiddleware = exports.errorHandler = exports.setup = void 0;
|
|
17
17
|
exports.dashboard = dashboard;
|
|
18
|
+
exports.shutdown = shutdown;
|
|
18
19
|
const express_1 = __importDefault(require("express"));
|
|
19
20
|
const middleware_js_1 = require("./middleware.js");
|
|
20
21
|
Object.defineProperty(exports, "errorHandler", { enumerable: true, get: function () { return middleware_js_1.errorHandler; } });
|
|
21
22
|
Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return middleware_js_1.setup; } });
|
|
22
23
|
const router_js_1 = require("./dashboard/router.js");
|
|
24
|
+
const shutdown_manager_js_1 = require("./shutdown-manager.js");
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
26
|
// Main middleware factory
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
@@ -40,14 +42,13 @@ exports.default = middleware_js_1.requestscope;
|
|
|
40
42
|
* @param adapter - Optional storage adapter instance
|
|
41
43
|
* @returns Express Router
|
|
42
44
|
*/
|
|
43
|
-
function dashboard(config, adapter) {
|
|
44
|
-
// Check
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
process.stderr.write('[RequestScope] Dashboard disabled in production environment (NODE_ENV=production)\n');
|
|
45
|
+
function dashboard(config, adapter, mode = 'development') {
|
|
46
|
+
// Check mode - if production, return disabled router
|
|
47
|
+
if (mode === 'production') {
|
|
48
|
+
process.stderr.write('[RequestScope] Dashboard disabled in production mode\n');
|
|
48
49
|
const router = express_1.default.Router();
|
|
49
50
|
router.use((req, res, next) => {
|
|
50
|
-
res.status(404).send('RequestScope dashboard is disabled in production
|
|
51
|
+
res.status(404).send('RequestScope dashboard is disabled in production mode');
|
|
51
52
|
});
|
|
52
53
|
return router;
|
|
53
54
|
}
|
|
@@ -56,6 +57,26 @@ function dashboard(config, adapter) {
|
|
|
56
57
|
// Attach dashboard as a named property on the default export for convenience
|
|
57
58
|
middleware_js_1.requestscope.dashboard = dashboard;
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
60
|
+
// Shutdown function
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Gracefully shuts down all RequestScope resources.
|
|
64
|
+
*
|
|
65
|
+
* This should be called by the host application during shutdown (e.g., on SIGTERM/SIGINT).
|
|
66
|
+
* It will:
|
|
67
|
+
* - Stop the retention scheduler
|
|
68
|
+
* - Drain the queue with a timeout
|
|
69
|
+
* - Destroy the queue (clear timers)
|
|
70
|
+
* - Close all database connections
|
|
71
|
+
*
|
|
72
|
+
* @param options - Optional configuration for shutdown
|
|
73
|
+
* @param options.drainTimeoutMs - Timeout for queue drain in milliseconds (default: 5000)
|
|
74
|
+
* @param options.logHandles - Whether to log active handles after shutdown (default: false)
|
|
75
|
+
*/
|
|
76
|
+
async function shutdown(options) {
|
|
77
|
+
await shutdown_manager_js_1.shutdownManager.shutdown(options);
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
59
80
|
// Named exports for convenience
|
|
60
81
|
// ---------------------------------------------------------------------------
|
|
61
82
|
var middleware_js_2 = require("./middleware.js");
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;AAwCH,8BAYC;AAmCD,4BAEC;AAtFD,sDAA8B;AAC9B,mDAA8F;AA+DrF,6FA/DwC,4BAAY,OA+DxC;AANZ,sFAzDsD,qBAAK,OAyDtD;AAxDd,qDAA8D;AAC9D,+DAAwD;AAWxD,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;;GAKG;AACH,kBAAe,4BAAsB,CAAC;AAEtC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,MAAwB,EAAE,OAAwB,EAAE,OAAqC,aAAa;IAC9H,qDAAqD;IACrD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,GAAQ,EAAE,IAAS,EAAE,EAAE;YAC3C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,IAAA,iCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,6EAA6E;AAC5E,4BAA8B,CAAC,SAAS,GAAG,SAAS,CAAC;AActD,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,QAAQ,CAAC,OAA2D;IACxF,MAAM,qCAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAgBD,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,iDAAyE;AAAhE,uHAAA,YAAY,OAA0B"}
|
package/dist/cjs/middleware.js
CHANGED
|
@@ -21,6 +21,7 @@ const retention_js_1 = require("./retention.js");
|
|
|
21
21
|
const capture_js_1 = require("./capture.js");
|
|
22
22
|
const router_js_1 = require("./dashboard/router.js");
|
|
23
23
|
const adapter_singleton_js_1 = require("./adapter-singleton.js");
|
|
24
|
+
const shutdown_manager_js_1 = require("./shutdown-manager.js");
|
|
24
25
|
const ALWAYS_IGNORED_PATHS = ['/favicon.ico', '/requestscope/api', '/requestscope/assets'];
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
26
27
|
// Factory function
|
|
@@ -49,21 +50,22 @@ const ALWAYS_IGNORED_PATHS = ['/favicon.ico', '/requestscope/api', '/requestscop
|
|
|
49
50
|
* @returns Express RequestHandler middleware
|
|
50
51
|
*/
|
|
51
52
|
function requestscope(config, adapter) {
|
|
52
|
-
// Check if running in production environment
|
|
53
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
54
|
-
if (nodeEnv === 'production') {
|
|
55
|
-
process.stderr.write('[RequestScope] Package disabled in production environment (NODE_ENV=production)\n');
|
|
56
|
-
return (req, res, next) => next();
|
|
57
|
-
}
|
|
58
53
|
// 1. Validate and apply defaults synchronously
|
|
59
54
|
(0, config_js_1.validateConfig)(config);
|
|
60
55
|
(0, config_js_1.applyDefaults)(config);
|
|
56
|
+
// 2. Check mode - if production, act as no-op
|
|
57
|
+
if (config.mode === 'production') {
|
|
58
|
+
process.stderr.write('[RequestScope] Running in production mode - request tracking disabled\n');
|
|
59
|
+
return (req, res, next) => next();
|
|
60
|
+
}
|
|
61
|
+
// 3. Log development mode
|
|
62
|
+
process.stderr.write('[RequestScope] Running in development mode - request tracking enabled\n');
|
|
61
63
|
const sensitiveFields = (0, config_js_1.buildSensitiveFieldSet)(config);
|
|
62
|
-
//
|
|
64
|
+
// 4. Get or create singleton adapter instance
|
|
63
65
|
if (!adapter) {
|
|
64
66
|
adapter = adapter_singleton_js_1.adapterSingleton.getOrCreate(config.storage);
|
|
65
67
|
}
|
|
66
|
-
//
|
|
68
|
+
// 5. Initialize adapter asynchronously (log errors, don't throw)
|
|
67
69
|
// Only initialize if not already initialized
|
|
68
70
|
void adapter.initialize().then(() => {
|
|
69
71
|
adapter_singleton_js_1.adapterSingleton.markInitialized(adapter, config.storage);
|
|
@@ -71,31 +73,22 @@ function requestscope(config, adapter) {
|
|
|
71
73
|
const message = err instanceof Error ? err.message : String(err);
|
|
72
74
|
process.stderr.write(`[RequestScope] Failed to initialize storage adapter: ${message}\n`);
|
|
73
75
|
});
|
|
74
|
-
//
|
|
76
|
+
// 6. Create and start AsyncQueue and RetentionScheduler
|
|
75
77
|
const queue = new queue_js_1.AsyncQueue(adapter);
|
|
76
78
|
const retentionDays = config.retentionDays ?? 30;
|
|
77
79
|
const retentionScheduler = new retention_js_1.RetentionScheduler(adapter, retentionDays);
|
|
78
80
|
retentionScheduler.start();
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
process[shutdownKey] = true;
|
|
91
|
-
process.on('SIGTERM', () => {
|
|
92
|
-
void shutdown();
|
|
93
|
-
});
|
|
94
|
-
process.on('SIGINT', () => {
|
|
95
|
-
void shutdown();
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
// 6. Return the request handler middleware
|
|
81
|
+
// 7. Register resources with shutdown manager
|
|
82
|
+
shutdown_manager_js_1.shutdownManager.registerResources({
|
|
83
|
+
queue: {
|
|
84
|
+
drain: (timeoutMs) => queue.drain(timeoutMs),
|
|
85
|
+
destroy: () => queue.destroy(),
|
|
86
|
+
},
|
|
87
|
+
retentionScheduler: {
|
|
88
|
+
stop: () => retentionScheduler.stop(),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// 8. Return the request handler middleware
|
|
99
92
|
return async (req, res, next) => {
|
|
100
93
|
// Check ignore list
|
|
101
94
|
const ignoreList = [...ALWAYS_IGNORED_PATHS, ...(config.ignore ?? [])];
|
|
@@ -139,15 +132,16 @@ function requestscope(config, adapter) {
|
|
|
139
132
|
* @param config - RequestScope configuration object
|
|
140
133
|
*/
|
|
141
134
|
function setup(app, config) {
|
|
142
|
-
// Check if running in production environment
|
|
143
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
144
|
-
if (nodeEnv === 'production') {
|
|
145
|
-
process.stderr.write('[RequestScope] Package disabled in production environment (NODE_ENV=production)\n');
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
135
|
// Validate and apply defaults
|
|
149
136
|
(0, config_js_1.validateConfig)(config);
|
|
150
137
|
(0, config_js_1.applyDefaults)(config);
|
|
138
|
+
// Check mode - if production, act as no-op
|
|
139
|
+
if (config.mode === 'production') {
|
|
140
|
+
process.stderr.write('[RequestScope] Running in production mode - request tracking disabled\n');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Log development mode
|
|
144
|
+
process.stderr.write('[RequestScope] Running in development mode - request tracking enabled\n');
|
|
151
145
|
// Get or create singleton adapter instance
|
|
152
146
|
const adapter = adapter_singleton_js_1.adapterSingleton.getOrCreate(config.storage);
|
|
153
147
|
// Initialize adapter asynchronously
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/retention.js
CHANGED
|
@@ -30,6 +30,10 @@ class RetentionScheduler {
|
|
|
30
30
|
this.intervalHandle = setInterval(() => {
|
|
31
31
|
void this.runOnce();
|
|
32
32
|
}, TWENTY_FOUR_HOURS_MS);
|
|
33
|
+
// Allow the Node.js process to exit even if this timer is still active.
|
|
34
|
+
if (this.intervalHandle.unref) {
|
|
35
|
+
this.intervalHandle.unref();
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* Stops the scheduled retention job. Inflight `runOnce()` calls are allowed
|
|
@@ -1 +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;
|
|
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;QAEzB,wEAAwE;QACxE,IAAI,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC9B,CAAC;IACH,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;AAjED,gDAiEC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ShutdownManager — Manages graceful shutdown of all RequestScope resources.
|
|
4
|
+
*
|
|
5
|
+
* This centralizes shutdown logic to prevent multiple shutdown executions,
|
|
6
|
+
* add comprehensive logging, and expose a shutdown() method that the host
|
|
7
|
+
* application can call instead of relying on global signal handlers.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.shutdownManager = void 0;
|
|
11
|
+
const adapter_singleton_js_1 = require("./adapter-singleton.js");
|
|
12
|
+
class ShutdownManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.isShuttingDown = false;
|
|
15
|
+
this.resources = {};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Registers resources that need to be cleaned up during shutdown.
|
|
19
|
+
*/
|
|
20
|
+
registerResources(resources) {
|
|
21
|
+
this.resources = { ...this.resources, ...resources };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Performs graceful shutdown of all registered resources.
|
|
25
|
+
*
|
|
26
|
+
* Steps:
|
|
27
|
+
* 1. Prevents multiple shutdown executions
|
|
28
|
+
* 2. Stops retention scheduler
|
|
29
|
+
* 3. Drains queue with timeout
|
|
30
|
+
* 4. Destroys queue
|
|
31
|
+
* 5. Closes all database connections
|
|
32
|
+
* 6. Logs remaining active handles for debugging
|
|
33
|
+
*/
|
|
34
|
+
async shutdown(options = {}) {
|
|
35
|
+
const { drainTimeoutMs = 5000, logHandles = false } = options;
|
|
36
|
+
// Prevent multiple shutdown executions
|
|
37
|
+
if (this.isShuttingDown) {
|
|
38
|
+
process.stderr.write('[RequestScope] Shutdown already in progress, skipping duplicate call\n');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.isShuttingDown = true;
|
|
42
|
+
process.stderr.write('[RequestScope] Starting graceful shutdown...\n');
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
try {
|
|
45
|
+
// 1. Stop retention scheduler
|
|
46
|
+
if (this.resources.retentionScheduler) {
|
|
47
|
+
process.stderr.write('[RequestScope] Stopping retention scheduler...\n');
|
|
48
|
+
this.resources.retentionScheduler.stop();
|
|
49
|
+
}
|
|
50
|
+
// 2. Drain queue with timeout
|
|
51
|
+
if (this.resources.queue) {
|
|
52
|
+
process.stderr.write(`[RequestScope] Draining queue (timeout: ${drainTimeoutMs}ms)...\n`);
|
|
53
|
+
try {
|
|
54
|
+
await Promise.race([
|
|
55
|
+
this.resources.queue.drain(drainTimeoutMs),
|
|
56
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Queue drain timeout')), drainTimeoutMs))
|
|
57
|
+
]);
|
|
58
|
+
process.stderr.write('[RequestScope] Queue drained successfully\n');
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62
|
+
process.stderr.write(`[RequestScope] Queue drain warning: ${message}\n`);
|
|
63
|
+
}
|
|
64
|
+
// 3. Destroy queue (clears timer)
|
|
65
|
+
process.stderr.write('[RequestScope] Destroying queue...\n');
|
|
66
|
+
this.resources.queue.destroy();
|
|
67
|
+
}
|
|
68
|
+
// 4. Close all database connections
|
|
69
|
+
process.stderr.write('[RequestScope] Closing database connections...\n');
|
|
70
|
+
await adapter_singleton_js_1.adapterSingleton.closeAll();
|
|
71
|
+
process.stderr.write('[RequestScope] Database connections closed\n');
|
|
72
|
+
const duration = Date.now() - startTime;
|
|
73
|
+
process.stderr.write(`[RequestScope] Graceful shutdown completed in ${duration}ms\n`);
|
|
74
|
+
// 5. Log remaining active handles for debugging
|
|
75
|
+
if (logHandles) {
|
|
76
|
+
this.logActiveHandles();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
process.stderr.write(`[RequestScope] Shutdown error: ${message}\n`);
|
|
82
|
+
// Log active handles even on error
|
|
83
|
+
if (logHandles) {
|
|
84
|
+
this.logActiveHandles();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Logs active handles to help identify what's preventing process exit.
|
|
90
|
+
*/
|
|
91
|
+
logActiveHandles() {
|
|
92
|
+
try {
|
|
93
|
+
// @ts-ignore - _getActiveHandles is not in TypeScript definitions
|
|
94
|
+
const handles = process._getActiveHandles();
|
|
95
|
+
process.stderr.write(`[RequestScope] Active handles (${handles.length}):\n`);
|
|
96
|
+
for (let i = 0; i < Math.min(handles.length, 20); i++) {
|
|
97
|
+
const handle = handles[i];
|
|
98
|
+
const type = handle.constructor.name;
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
const details = handle._handle?.type || '';
|
|
101
|
+
process.stderr.write(` [${i}] ${type}${details ? ` (${details})` : ''}\n`);
|
|
102
|
+
}
|
|
103
|
+
if (handles.length > 20) {
|
|
104
|
+
process.stderr.write(` ... and ${handles.length - 20} more\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
process.stderr.write('[RequestScope] Could not log active handles\n');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns whether shutdown is currently in progress.
|
|
113
|
+
*/
|
|
114
|
+
isShutdownInProgress() {
|
|
115
|
+
return this.isShuttingDown;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Export singleton instance
|
|
119
|
+
exports.shutdownManager = new ShutdownManager();
|
|
120
|
+
//# sourceMappingURL=shutdown-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown-manager.js","sourceRoot":"","sources":["../../src/shutdown-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/config.js
CHANGED
|
@@ -39,6 +39,9 @@ const DEFAULT_SENSITIVE_FIELDS = [
|
|
|
39
39
|
* storage.ssl → false
|
|
40
40
|
*/
|
|
41
41
|
export function applyDefaults(config) {
|
|
42
|
+
if (config.mode === undefined) {
|
|
43
|
+
config.mode = 'development';
|
|
44
|
+
}
|
|
42
45
|
if (config.retentionDays === undefined) {
|
|
43
46
|
config.retentionDays = 30;
|
|
44
47
|
}
|
|
@@ -70,25 +73,29 @@ export function applyDefaults(config) {
|
|
|
70
73
|
* 4. `retentionDays` (when provided) must be a positive integer in [1, 365].
|
|
71
74
|
*/
|
|
72
75
|
export function validateConfig(config) {
|
|
73
|
-
const { storage, retentionDays } = config;
|
|
74
|
-
// 1. Validate
|
|
76
|
+
const { storage, retentionDays, mode } = config;
|
|
77
|
+
// 1. Validate mode
|
|
78
|
+
if (mode !== undefined && mode !== 'development' && mode !== 'production') {
|
|
79
|
+
throw new Error(`Invalid mode: '${mode}'. Supported values are: development, production`);
|
|
80
|
+
}
|
|
81
|
+
// 2. Validate storage.type
|
|
75
82
|
if (!SUPPORTED_STORAGE_TYPES.includes(storage.type)) {
|
|
76
83
|
throw new Error(`Invalid storage type: '${storage.type}'. Supported values are: mongodb, mysql, postgresql`);
|
|
77
84
|
}
|
|
78
|
-
//
|
|
85
|
+
// 3. MongoDB: uri is required
|
|
79
86
|
if (storage.type === 'mongodb') {
|
|
80
87
|
if (!storage.uri) {
|
|
81
88
|
throw new Error("storage.uri is required when storage.type is 'mongodb'");
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
|
-
//
|
|
91
|
+
// 4. MySQL / PostgreSQL: all SQL required fields must be present
|
|
85
92
|
if (storage.type === 'mysql' || storage.type === 'postgresql') {
|
|
86
93
|
const missing = SQL_REQUIRED_FIELDS.filter((field) => storage[field] === undefined || storage[field] === null);
|
|
87
94
|
if (missing.length > 0) {
|
|
88
95
|
throw new Error(`Missing required storage fields: ${missing.join(', ')}`);
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
|
-
//
|
|
98
|
+
// 5. retentionDays: must be a positive integer in [1, 365] when provided
|
|
92
99
|
if (retentionDays !== undefined) {
|
|
93
100
|
const isValidInteger = typeof retentionDays === 'number' &&
|
|
94
101
|
Number.isInteger(retentionDays) &&
|
package/dist/esm/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import express from 'express';
|
|
12
12
|
import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware.js';
|
|
13
13
|
import { createDashboardRouter } from './dashboard/router.js';
|
|
14
|
+
import { shutdownManager } from './shutdown-manager.js';
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Main middleware factory
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -31,14 +32,13 @@ export default requestscopeMiddleware;
|
|
|
31
32
|
* @param adapter - Optional storage adapter instance
|
|
32
33
|
* @returns Express Router
|
|
33
34
|
*/
|
|
34
|
-
export function dashboard(config, adapter) {
|
|
35
|
-
// Check
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.stderr.write('[RequestScope] Dashboard disabled in production environment (NODE_ENV=production)\n');
|
|
35
|
+
export function dashboard(config, adapter, mode = 'development') {
|
|
36
|
+
// Check mode - if production, return disabled router
|
|
37
|
+
if (mode === 'production') {
|
|
38
|
+
process.stderr.write('[RequestScope] Dashboard disabled in production mode\n');
|
|
39
39
|
const router = express.Router();
|
|
40
40
|
router.use((req, res, next) => {
|
|
41
|
-
res.status(404).send('RequestScope dashboard is disabled in production
|
|
41
|
+
res.status(404).send('RequestScope dashboard is disabled in production mode');
|
|
42
42
|
});
|
|
43
43
|
return router;
|
|
44
44
|
}
|
|
@@ -55,6 +55,26 @@ export { setup };
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
export { errorHandler };
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
|
+
// Shutdown function
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Gracefully shuts down all RequestScope resources.
|
|
62
|
+
*
|
|
63
|
+
* This should be called by the host application during shutdown (e.g., on SIGTERM/SIGINT).
|
|
64
|
+
* It will:
|
|
65
|
+
* - Stop the retention scheduler
|
|
66
|
+
* - Drain the queue with a timeout
|
|
67
|
+
* - Destroy the queue (clear timers)
|
|
68
|
+
* - Close all database connections
|
|
69
|
+
*
|
|
70
|
+
* @param options - Optional configuration for shutdown
|
|
71
|
+
* @param options.drainTimeoutMs - Timeout for queue drain in milliseconds (default: 5000)
|
|
72
|
+
* @param options.logHandles - Whether to log active handles after shutdown (default: false)
|
|
73
|
+
*/
|
|
74
|
+
export async function shutdown(options) {
|
|
75
|
+
await shutdownManager.shutdown(options);
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
58
78
|
// Named exports for convenience
|
|
59
79
|
// ---------------------------------------------------------------------------
|
|
60
80
|
export { requestscope as requestscopeMiddleware } from './middleware.js';
|
package/dist/esm/middleware.js
CHANGED
|
@@ -16,6 +16,7 @@ import { RetentionScheduler } from './retention.js';
|
|
|
16
16
|
import { bufferRequestBody, wrapResponse, normalizeRequestUrl, ERROR_DATA_SYMBOL, } from './capture.js';
|
|
17
17
|
import { createDashboardRouter } from './dashboard/router.js';
|
|
18
18
|
import { adapterSingleton } from './adapter-singleton.js';
|
|
19
|
+
import { shutdownManager } from './shutdown-manager.js';
|
|
19
20
|
const ALWAYS_IGNORED_PATHS = ['/favicon.ico', '/requestscope/api', '/requestscope/assets'];
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Factory function
|
|
@@ -44,21 +45,22 @@ const ALWAYS_IGNORED_PATHS = ['/favicon.ico', '/requestscope/api', '/requestscop
|
|
|
44
45
|
* @returns Express RequestHandler middleware
|
|
45
46
|
*/
|
|
46
47
|
export function requestscope(config, adapter) {
|
|
47
|
-
// Check if running in production environment
|
|
48
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
49
|
-
if (nodeEnv === 'production') {
|
|
50
|
-
process.stderr.write('[RequestScope] Package disabled in production environment (NODE_ENV=production)\n');
|
|
51
|
-
return (req, res, next) => next();
|
|
52
|
-
}
|
|
53
48
|
// 1. Validate and apply defaults synchronously
|
|
54
49
|
validateConfig(config);
|
|
55
50
|
applyDefaults(config);
|
|
51
|
+
// 2. Check mode - if production, act as no-op
|
|
52
|
+
if (config.mode === 'production') {
|
|
53
|
+
process.stderr.write('[RequestScope] Running in production mode - request tracking disabled\n');
|
|
54
|
+
return (req, res, next) => next();
|
|
55
|
+
}
|
|
56
|
+
// 3. Log development mode
|
|
57
|
+
process.stderr.write('[RequestScope] Running in development mode - request tracking enabled\n');
|
|
56
58
|
const sensitiveFields = buildSensitiveFieldSet(config);
|
|
57
|
-
//
|
|
59
|
+
// 4. Get or create singleton adapter instance
|
|
58
60
|
if (!adapter) {
|
|
59
61
|
adapter = adapterSingleton.getOrCreate(config.storage);
|
|
60
62
|
}
|
|
61
|
-
//
|
|
63
|
+
// 5. Initialize adapter asynchronously (log errors, don't throw)
|
|
62
64
|
// Only initialize if not already initialized
|
|
63
65
|
void adapter.initialize().then(() => {
|
|
64
66
|
adapterSingleton.markInitialized(adapter, config.storage);
|
|
@@ -66,31 +68,22 @@ export function requestscope(config, adapter) {
|
|
|
66
68
|
const message = err instanceof Error ? err.message : String(err);
|
|
67
69
|
process.stderr.write(`[RequestScope] Failed to initialize storage adapter: ${message}\n`);
|
|
68
70
|
});
|
|
69
|
-
//
|
|
71
|
+
// 6. Create and start AsyncQueue and RetentionScheduler
|
|
70
72
|
const queue = new AsyncQueue(adapter);
|
|
71
73
|
const retentionDays = config.retentionDays ?? 30;
|
|
72
74
|
const retentionScheduler = new RetentionScheduler(adapter, retentionDays);
|
|
73
75
|
retentionScheduler.start();
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
process[shutdownKey] = true;
|
|
86
|
-
process.on('SIGTERM', () => {
|
|
87
|
-
void shutdown();
|
|
88
|
-
});
|
|
89
|
-
process.on('SIGINT', () => {
|
|
90
|
-
void shutdown();
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
// 6. Return the request handler middleware
|
|
76
|
+
// 7. Register resources with shutdown manager
|
|
77
|
+
shutdownManager.registerResources({
|
|
78
|
+
queue: {
|
|
79
|
+
drain: (timeoutMs) => queue.drain(timeoutMs),
|
|
80
|
+
destroy: () => queue.destroy(),
|
|
81
|
+
},
|
|
82
|
+
retentionScheduler: {
|
|
83
|
+
stop: () => retentionScheduler.stop(),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
// 8. Return the request handler middleware
|
|
94
87
|
return async (req, res, next) => {
|
|
95
88
|
// Check ignore list
|
|
96
89
|
const ignoreList = [...ALWAYS_IGNORED_PATHS, ...(config.ignore ?? [])];
|
|
@@ -134,15 +127,16 @@ export function requestscope(config, adapter) {
|
|
|
134
127
|
* @param config - RequestScope configuration object
|
|
135
128
|
*/
|
|
136
129
|
export function setup(app, config) {
|
|
137
|
-
// Check if running in production environment
|
|
138
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
139
|
-
if (nodeEnv === 'production') {
|
|
140
|
-
process.stderr.write('[RequestScope] Package disabled in production environment (NODE_ENV=production)\n');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
130
|
// Validate and apply defaults
|
|
144
131
|
validateConfig(config);
|
|
145
132
|
applyDefaults(config);
|
|
133
|
+
// Check mode - if production, act as no-op
|
|
134
|
+
if (config.mode === 'production') {
|
|
135
|
+
process.stderr.write('[RequestScope] Running in production mode - request tracking disabled\n');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Log development mode
|
|
139
|
+
process.stderr.write('[RequestScope] Running in development mode - request tracking enabled\n');
|
|
146
140
|
// Get or create singleton adapter instance
|
|
147
141
|
const adapter = adapterSingleton.getOrCreate(config.storage);
|
|
148
142
|
// Initialize adapter asynchronously
|
package/dist/esm/retention.js
CHANGED
|
@@ -27,6 +27,10 @@ export class RetentionScheduler {
|
|
|
27
27
|
this.intervalHandle = setInterval(() => {
|
|
28
28
|
void this.runOnce();
|
|
29
29
|
}, TWENTY_FOUR_HOURS_MS);
|
|
30
|
+
// Allow the Node.js process to exit even if this timer is still active.
|
|
31
|
+
if (this.intervalHandle.unref) {
|
|
32
|
+
this.intervalHandle.unref();
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* Stops the scheduled retention job. Inflight `runOnce()` calls are allowed
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShutdownManager — Manages graceful shutdown of all RequestScope resources.
|
|
3
|
+
*
|
|
4
|
+
* This centralizes shutdown logic to prevent multiple shutdown executions,
|
|
5
|
+
* add comprehensive logging, and expose a shutdown() method that the host
|
|
6
|
+
* application can call instead of relying on global signal handlers.
|
|
7
|
+
*/
|
|
8
|
+
import { adapterSingleton } from './adapter-singleton.js';
|
|
9
|
+
class ShutdownManager {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.isShuttingDown = false;
|
|
12
|
+
this.resources = {};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Registers resources that need to be cleaned up during shutdown.
|
|
16
|
+
*/
|
|
17
|
+
registerResources(resources) {
|
|
18
|
+
this.resources = { ...this.resources, ...resources };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Performs graceful shutdown of all registered resources.
|
|
22
|
+
*
|
|
23
|
+
* Steps:
|
|
24
|
+
* 1. Prevents multiple shutdown executions
|
|
25
|
+
* 2. Stops retention scheduler
|
|
26
|
+
* 3. Drains queue with timeout
|
|
27
|
+
* 4. Destroys queue
|
|
28
|
+
* 5. Closes all database connections
|
|
29
|
+
* 6. Logs remaining active handles for debugging
|
|
30
|
+
*/
|
|
31
|
+
async shutdown(options = {}) {
|
|
32
|
+
const { drainTimeoutMs = 5000, logHandles = false } = options;
|
|
33
|
+
// Prevent multiple shutdown executions
|
|
34
|
+
if (this.isShuttingDown) {
|
|
35
|
+
process.stderr.write('[RequestScope] Shutdown already in progress, skipping duplicate call\n');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.isShuttingDown = true;
|
|
39
|
+
process.stderr.write('[RequestScope] Starting graceful shutdown...\n');
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
try {
|
|
42
|
+
// 1. Stop retention scheduler
|
|
43
|
+
if (this.resources.retentionScheduler) {
|
|
44
|
+
process.stderr.write('[RequestScope] Stopping retention scheduler...\n');
|
|
45
|
+
this.resources.retentionScheduler.stop();
|
|
46
|
+
}
|
|
47
|
+
// 2. Drain queue with timeout
|
|
48
|
+
if (this.resources.queue) {
|
|
49
|
+
process.stderr.write(`[RequestScope] Draining queue (timeout: ${drainTimeoutMs}ms)...\n`);
|
|
50
|
+
try {
|
|
51
|
+
await Promise.race([
|
|
52
|
+
this.resources.queue.drain(drainTimeoutMs),
|
|
53
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Queue drain timeout')), drainTimeoutMs))
|
|
54
|
+
]);
|
|
55
|
+
process.stderr.write('[RequestScope] Queue drained successfully\n');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
process.stderr.write(`[RequestScope] Queue drain warning: ${message}\n`);
|
|
60
|
+
}
|
|
61
|
+
// 3. Destroy queue (clears timer)
|
|
62
|
+
process.stderr.write('[RequestScope] Destroying queue...\n');
|
|
63
|
+
this.resources.queue.destroy();
|
|
64
|
+
}
|
|
65
|
+
// 4. Close all database connections
|
|
66
|
+
process.stderr.write('[RequestScope] Closing database connections...\n');
|
|
67
|
+
await adapterSingleton.closeAll();
|
|
68
|
+
process.stderr.write('[RequestScope] Database connections closed\n');
|
|
69
|
+
const duration = Date.now() - startTime;
|
|
70
|
+
process.stderr.write(`[RequestScope] Graceful shutdown completed in ${duration}ms\n`);
|
|
71
|
+
// 5. Log remaining active handles for debugging
|
|
72
|
+
if (logHandles) {
|
|
73
|
+
this.logActiveHandles();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
+
process.stderr.write(`[RequestScope] Shutdown error: ${message}\n`);
|
|
79
|
+
// Log active handles even on error
|
|
80
|
+
if (logHandles) {
|
|
81
|
+
this.logActiveHandles();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Logs active handles to help identify what's preventing process exit.
|
|
87
|
+
*/
|
|
88
|
+
logActiveHandles() {
|
|
89
|
+
try {
|
|
90
|
+
// @ts-ignore - _getActiveHandles is not in TypeScript definitions
|
|
91
|
+
const handles = process._getActiveHandles();
|
|
92
|
+
process.stderr.write(`[RequestScope] Active handles (${handles.length}):\n`);
|
|
93
|
+
for (let i = 0; i < Math.min(handles.length, 20); i++) {
|
|
94
|
+
const handle = handles[i];
|
|
95
|
+
const type = handle.constructor.name;
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
const details = handle._handle?.type || '';
|
|
98
|
+
process.stderr.write(` [${i}] ${type}${details ? ` (${details})` : ''}\n`);
|
|
99
|
+
}
|
|
100
|
+
if (handles.length > 20) {
|
|
101
|
+
process.stderr.write(` ... and ${handles.length - 20} more\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
process.stderr.write('[RequestScope] Could not log active handles\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns whether shutdown is currently in progress.
|
|
110
|
+
*/
|
|
111
|
+
isShutdownInProgress() {
|
|
112
|
+
return this.isShuttingDown;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Export singleton instance
|
|
116
|
+
export const shutdownManager = new ShutdownManager();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,8 +25,26 @@ export default requestscopeMiddleware;
|
|
|
25
25
|
* @param adapter - Optional storage adapter instance
|
|
26
26
|
* @returns Express Router
|
|
27
27
|
*/
|
|
28
|
-
export declare function dashboard(config?: DashboardConfig, adapter?: StorageAdapter): express.Router;
|
|
28
|
+
export declare function dashboard(config?: DashboardConfig, adapter?: StorageAdapter, mode?: 'development' | 'production'): express.Router;
|
|
29
29
|
export { setup };
|
|
30
30
|
export { errorHandler };
|
|
31
|
+
/**
|
|
32
|
+
* Gracefully shuts down all RequestScope resources.
|
|
33
|
+
*
|
|
34
|
+
* This should be called by the host application during shutdown (e.g., on SIGTERM/SIGINT).
|
|
35
|
+
* It will:
|
|
36
|
+
* - Stop the retention scheduler
|
|
37
|
+
* - Drain the queue with a timeout
|
|
38
|
+
* - Destroy the queue (clear timers)
|
|
39
|
+
* - Close all database connections
|
|
40
|
+
*
|
|
41
|
+
* @param options - Optional configuration for shutdown
|
|
42
|
+
* @param options.drainTimeoutMs - Timeout for queue drain in milliseconds (default: 5000)
|
|
43
|
+
* @param options.logHandles - Whether to log active handles after shutdown (default: false)
|
|
44
|
+
*/
|
|
45
|
+
export declare function shutdown(options?: {
|
|
46
|
+
drainTimeoutMs?: number;
|
|
47
|
+
logHandles?: boolean;
|
|
48
|
+
}): Promise<void>;
|
|
31
49
|
export type { RequestScopeConfig, StorageConfig, AuthConfig, RequestRecord, StorageAdapter, QueryFilters, DashboardConfig, };
|
|
32
50
|
export { requestscope as requestscopeMiddleware } from './middleware.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShutdownManager — Manages graceful shutdown of all RequestScope resources.
|
|
3
|
+
*
|
|
4
|
+
* This centralizes shutdown logic to prevent multiple shutdown executions,
|
|
5
|
+
* add comprehensive logging, and expose a shutdown() method that the host
|
|
6
|
+
* application can call instead of relying on global signal handlers.
|
|
7
|
+
*/
|
|
8
|
+
interface ShutdownResources {
|
|
9
|
+
queue?: {
|
|
10
|
+
drain: (timeoutMs: number) => Promise<void>;
|
|
11
|
+
destroy: () => void;
|
|
12
|
+
};
|
|
13
|
+
retentionScheduler?: {
|
|
14
|
+
stop: () => void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
declare class ShutdownManager {
|
|
18
|
+
private isShuttingDown;
|
|
19
|
+
private resources;
|
|
20
|
+
/**
|
|
21
|
+
* Registers resources that need to be cleaned up during shutdown.
|
|
22
|
+
*/
|
|
23
|
+
registerResources(resources: ShutdownResources): void;
|
|
24
|
+
/**
|
|
25
|
+
* Performs graceful shutdown of all registered resources.
|
|
26
|
+
*
|
|
27
|
+
* Steps:
|
|
28
|
+
* 1. Prevents multiple shutdown executions
|
|
29
|
+
* 2. Stops retention scheduler
|
|
30
|
+
* 3. Drains queue with timeout
|
|
31
|
+
* 4. Destroys queue
|
|
32
|
+
* 5. Closes all database connections
|
|
33
|
+
* 6. Logs remaining active handles for debugging
|
|
34
|
+
*/
|
|
35
|
+
shutdown(options?: {
|
|
36
|
+
drainTimeoutMs?: number;
|
|
37
|
+
logHandles?: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Logs active handles to help identify what's preventing process exit.
|
|
41
|
+
*/
|
|
42
|
+
private logActiveHandles;
|
|
43
|
+
/**
|
|
44
|
+
* Returns whether shutdown is currently in progress.
|
|
45
|
+
*/
|
|
46
|
+
isShutdownInProgress(): boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare const shutdownManager: ShutdownManager;
|
|
49
|
+
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface DashboardConfig {
|
|
|
69
69
|
}
|
|
70
70
|
export interface RequestScopeConfig {
|
|
71
71
|
storage: StorageConfig;
|
|
72
|
+
/** Package mode: 'development' enables all features, 'production' disables all tracking. Default: 'development' */
|
|
73
|
+
mode?: 'development' | 'production';
|
|
72
74
|
/** Number of days to retain records. Range: 1–365. Default: 30 */
|
|
73
75
|
retentionDays?: number;
|
|
74
76
|
/** Exact URL paths to skip (no capture). Default: [] */
|