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 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
 
@@ -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 storage.type
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
- // 2. MongoDB: uri is required
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
- // 3. MySQL / PostgreSQL: all SQL required fields must be present
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
- // 4. retentionDays: must be a positive integer in [1, 365] when provided
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) &&
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
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 if running in production environment
45
- const nodeEnv = process.env.NODE_ENV;
46
- if (nodeEnv === 'production') {
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 environment');
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");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;AAuCH,8BAaC;AAjDD,sDAA8B;AAC9B,mDAA8F;AA+DrF,6FA/DwC,4BAAY,OA+DxC;AANZ,sFAzDsD,qBAAK,OAyDtD;AAxDd,qDAA8D;AAW9D,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;IAC1E,6CAA6C;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrC,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qFAAqF,CAAC,CAAC;QAC5G,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,8DAA8D,CAAC,CAAC;QACvF,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;AA4BtD,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,iDAAyE;AAAhE,uHAAA,YAAY,OAA0B"}
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"}
@@ -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
- // 2. Get or create singleton adapter instance
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
- // 3. Initialize adapter asynchronously (log errors, don't throw)
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
- // 4. Create and start AsyncQueue and RetentionScheduler
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
- // 5. Attach graceful shutdown handlers (only once per process)
80
- const shutdown = async () => {
81
- retentionScheduler.stop();
82
- await queue.drain(10000);
83
- queue.destroy();
84
- // Close all database connections via singleton
85
- await adapter_singleton_js_1.adapterSingleton.closeAll();
86
- };
87
- // Use a flag to ensure shutdown handlers are only attached once
88
- const shutdownKey = '__requestscope_shutdown_attached__';
89
- if (!process[shutdownKey]) {
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":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -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;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"}
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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"}
@@ -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 storage.type
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
- // 2. MongoDB: uri is required
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
- // 3. MySQL / PostgreSQL: all SQL required fields must be present
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
- // 4. retentionDays: must be a positive integer in [1, 365] when provided
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 if running in production environment
36
- const nodeEnv = process.env.NODE_ENV;
37
- if (nodeEnv === 'production') {
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 environment');
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';
@@ -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
- // 2. Get or create singleton adapter instance
59
+ // 4. Get or create singleton adapter instance
58
60
  if (!adapter) {
59
61
  adapter = adapterSingleton.getOrCreate(config.storage);
60
62
  }
61
- // 3. Initialize adapter asynchronously (log errors, don't throw)
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
- // 4. Create and start AsyncQueue and RetentionScheduler
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
- // 5. Attach graceful shutdown handlers (only once per process)
75
- const shutdown = async () => {
76
- retentionScheduler.stop();
77
- await queue.drain(10000);
78
- queue.destroy();
79
- // Close all database connections via singleton
80
- await adapterSingleton.closeAll();
81
- };
82
- // Use a flag to ensure shutdown handlers are only attached once
83
- const shutdownKey = '__requestscope_shutdown_attached__';
84
- if (!process[shutdownKey]) {
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
@@ -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();
@@ -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 {};
@@ -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: [] */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "request-scope-api",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Zero-friction API request observability for Express.js applications",
5
5
  "keywords": [
6
6
  "express",