dt-common-device 13.10.4 → 13.10.6

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.
@@ -20,7 +20,8 @@ export declare enum Resource {
20
20
  ACCESS_BLE = "accessBle",
21
21
  EMERGENCY_CODE = "emergencyCode",
22
22
  EMERGENCY_RFID = "emergencyRfid",
23
- EMERGENCY_BLE = "emergencyBle"
23
+ EMERGENCY_BLE = "emergencyBle",
24
+ SERVER = "server"
24
25
  }
25
26
  export interface IAuditProperties {
26
27
  resource: Resource;
@@ -45,3 +46,8 @@ export interface IAuditProperties {
45
46
  eventData?: any;
46
47
  [key: string]: any;
47
48
  }
49
+ export type PartialAuditProperties = Omit<IAuditProperties, "resource" | "source" | "propertyId"> & {
50
+ resource?: Resource;
51
+ source?: Source;
52
+ propertyId?: string;
53
+ };
@@ -24,4 +24,5 @@ var Resource;
24
24
  Resource["EMERGENCY_CODE"] = "emergencyCode";
25
25
  Resource["EMERGENCY_RFID"] = "emergencyRfid";
26
26
  Resource["EMERGENCY_BLE"] = "emergencyBle";
27
+ Resource["SERVER"] = "server";
27
28
  })(Resource || (exports.Resource = Resource = {}));
@@ -1,10 +1,21 @@
1
- import { IAuditProperties } from "./IAuditProperties";
1
+ import { IAuditProperties, PartialAuditProperties } from "./IAuditProperties";
2
2
  /**
3
3
  * Publishes an audit event. Failures are logged and swallowed so callers can
4
4
  * fire-and-forget without causing unhandled promise rejections when the audit
5
5
  * pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
6
+ * USE FOR PROPERTY LEVEL AUDITS
6
7
  */
7
8
  export declare function pushAudit(data: {
8
9
  auditType: string;
9
10
  auditData: IAuditProperties;
10
11
  }): Promise<void>;
12
+ /**
13
+ * Publishes an audit event. Failures are logged and swallowed so callers can
14
+ * fire-and-forget without causing unhandled promise rejections when the audit
15
+ * pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
16
+ * USE FOR SYSTEM LEVEL AUDITS (If Property ID is not available)
17
+ */
18
+ export declare function pushSystemAudit(data: {
19
+ auditType: string;
20
+ auditData: PartialAuditProperties;
21
+ }): Promise<void>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.pushAudit = pushAudit;
7
+ exports.pushSystemAudit = pushSystemAudit;
7
8
  const dt_audit_library_1 = require("dt-audit-library");
8
9
  const config_1 = require("../config/config");
9
10
  const AuditUtils_1 = require("./AuditUtils");
@@ -12,6 +13,7 @@ const typedi_1 = __importDefault(require("typedi"));
12
13
  * Publishes an audit event. Failures are logged and swallowed so callers can
13
14
  * fire-and-forget without causing unhandled promise rejections when the audit
14
15
  * pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
16
+ * USE FOR PROPERTY LEVEL AUDITS
15
17
  */
16
18
  async function pushAudit(data) {
17
19
  try {
@@ -33,3 +35,28 @@ async function pushAudit(data) {
33
35
  : { auditType: data.auditType });
34
36
  }
35
37
  }
38
+ /**
39
+ * Publishes an audit event. Failures are logged and swallowed so callers can
40
+ * fire-and-forget without causing unhandled promise rejections when the audit
41
+ * pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
42
+ * USE FOR SYSTEM LEVEL AUDITS (If Property ID is not available)
43
+ */
44
+ async function pushSystemAudit(data) {
45
+ try {
46
+ await (0, dt_audit_library_1.publishAudit)({
47
+ eventType: data.auditType,
48
+ properties: {
49
+ ...data.auditData,
50
+ timestamp: new Date().toISOString(),
51
+ env_type: process.env.NODE_ENV,
52
+ },
53
+ });
54
+ }
55
+ catch (err) {
56
+ const status = err?.response?.status;
57
+ const code = err?.code;
58
+ (0, config_1.getLogger)().error(`pushSystemAudit failed for ${data.auditType}: ${err?.message ?? err}`, status != null || code != null
59
+ ? { auditType: data.auditType, status, code }
60
+ : { auditType: data.auditType });
61
+ }
62
+ }
@@ -99,7 +99,7 @@ let HybridHttpQueue = (() => {
99
99
  (0, config_1.getConfig)().LOGGER.info(`Request queued: ${method} ${url} -> ${provider} [${connectionId}]. Job ID: ${jobId}, Delay: ${delay}ms`);
100
100
  // Wait for job completion and return result
101
101
  // Simple: delay + windowMs + HTTP buffer timeout
102
- return queueUtils_1.QueueUtils.waitForJobCompletion(jobId, queueKey, delay, windowMs);
102
+ return queueUtils_1.QueueUtils.waitForJobCompletion(jobId, queueKey, delay, windowMs, this.queues);
103
103
  }
104
104
  async processHttpRequest(job) {
105
105
  // Log immediately when worker picks up the job
@@ -147,7 +147,7 @@ let HybridHttpQueue = (() => {
147
147
  return this.handleRequest(url, method, httpCallOption);
148
148
  }
149
149
  async handleRequest(url, method, options) {
150
- const { connectionId, provider, microservice } = jobUtils_1.JobUtils.extractConnectionDetails(options);
150
+ const { connectionId, provider } = jobUtils_1.JobUtils.extractConnectionDetails(options);
151
151
  // Check rate limit first
152
152
  const allowed = await rateLimit_utils_1.RateLimitUtils.isRateLimitAllowed(connectionId, provider, this.rateLimitConfigs);
153
153
  if (allowed) {
@@ -8,5 +8,5 @@ export declare class QueueUtils {
8
8
  static waitForRateLimitExpiry(connectionId: string, provider: string, rateLimitConfigs: Map<string, IRateLimitConfig>): Promise<void>;
9
9
  static executeHttpRequest(url: string, method: string, options: HttpCallOption, connectionId: string, provider: string): Promise<any>;
10
10
  static addJobToQueue(queueKey: string, jobData: any, delay: number, queues: Map<string, any>): Promise<string>;
11
- static waitForJobCompletion(jobId: string, queueKey: string, jobDelay?: number, windowMs?: number): Promise<any>;
11
+ static waitForJobCompletion(jobId: string, queueKey: string, jobDelay: number | undefined, windowMs: number | undefined, queues: Map<string, any>): Promise<any>;
12
12
  }
@@ -35,6 +35,8 @@ class QueueUtils {
35
35
  console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Existing worker running status: ${isRunning}`);
36
36
  if (!isRunning) {
37
37
  console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker not running, closing and recreating...`);
38
+ // Stale worker detected — surface at WARN level with count context
39
+ (0, config_1.getConfig)().LOGGER.warn(`Stale worker for queue ${queueKey} (not running), recreating. Active workers before recreate: ${workers.size}`);
38
40
  try {
39
41
  existingWorker.close();
40
42
  }
@@ -116,7 +118,8 @@ class QueueUtils {
116
118
  });
117
119
  workers.set(queueKey, worker);
118
120
  console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker initialized and stored for queue: ${queueKey}`);
119
- (0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey}`);
121
+ // Log total active workers — a growing count here signals a connection-proliferation leak
122
+ (0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey} (total active workers: ${workers.size})`);
120
123
  }
121
124
  static async waitForRateLimitExpiry(connectionId, provider, rateLimitConfigs) {
122
125
  const key = `rate_limit:${provider}:${connectionId}`;
@@ -202,22 +205,25 @@ class QueueUtils {
202
205
  queue.getActiveCount(),
203
206
  ]);
204
207
  console.log(`[${new Date().toISOString()}] [addJobToQueue] Queue state - QueueKey: ${queueKey}, Waiting: ${waiting}, Delayed: ${delayed}, Active: ${active}`);
208
+ // Warn via structured logger when backlog is building — correlates with heap stats
209
+ if (waiting + delayed > 1) {
210
+ (0, config_1.getConfig)().LOGGER.warn(`Queue backlog [${queueKey}]: waiting=${waiting} delayed=${delayed} active=${active}`);
211
+ }
205
212
  }
206
213
  catch (e) {
207
214
  // Ignore errors getting queue state
208
215
  }
209
216
  return job.id;
210
217
  }
211
- static async waitForJobCompletion(jobId, queueKey, jobDelay = 0, windowMs = 60000) {
218
+ static async waitForJobCompletion(jobId, queueKey, jobDelay = 0, windowMs = 60000, queues) {
212
219
  // Simple: delay (wait) + windowMs (execute) + HTTP timeout buffer
213
220
  const httpTimeout = 60000;
214
221
  const totalTimeout = jobDelay + windowMs + httpTimeout;
215
222
  console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Waiting for job ${jobId} - delay: ${jobDelay}ms, timeout: ${totalTimeout}ms`);
216
223
  const startTime = Date.now();
217
- const { Queue } = require("bullmq");
218
- const queue = new Queue(queueKey, {
219
- connection: (0, redis_1.getRedisClient)(),
220
- });
224
+ // Reuse the existing queue instance — never create a new Queue per call
225
+ // (each new Queue duplicates the Redis connection, causing a connection leak)
226
+ const queue = this.getOrCreateQueue(queueKey, queues);
221
227
  return new Promise(async (resolve, reject) => {
222
228
  const checkInterval = setInterval(async () => {
223
229
  try {
@@ -226,7 +232,6 @@ class QueueUtils {
226
232
  const elapsed = Date.now() - startTime;
227
233
  if (elapsed > totalTimeout) {
228
234
  clearInterval(checkInterval);
229
- await queue.close();
230
235
  return reject(new Error(`Job ${jobId} not found and timeout exceeded`));
231
236
  }
232
237
  return; // Job not found yet, keep checking
@@ -236,26 +241,25 @@ class QueueUtils {
236
241
  console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Job ${jobId} completed`);
237
242
  clearInterval(checkInterval);
238
243
  const result = job.returnvalue;
239
- await queue.close();
240
244
  return resolve(result);
241
245
  }
242
246
  if (state === "failed") {
243
247
  console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Job ${jobId} failed: ${job.failedReason}`);
244
248
  clearInterval(checkInterval);
245
- await queue.close();
246
249
  return reject(new Error(job.failedReason || "Job failed"));
247
250
  }
248
251
  }
249
252
  catch (error) {
250
253
  clearInterval(checkInterval);
251
- await queue.close();
252
254
  reject(error);
253
255
  }
254
256
  }, 500); // Check every 500ms
255
257
  // Timeout
256
258
  setTimeout(() => {
257
259
  clearInterval(checkInterval);
258
- queue.close();
260
+ // Log via structured logger before rejecting so the timeout is visible in
261
+ // CloudWatch alongside the periodic heap stats — correlates queue stalls with memory pressure.
262
+ (0, config_1.getConfig)().LOGGER.error(`Job ${jobId} timed out after ${totalTimeout}ms [${queueKey}] (jobDelay=${jobDelay}ms)`);
259
263
  reject(new Error(`Request timeout: Maximum wait time exceeded (${totalTimeout}ms). Job delay was ${jobDelay}ms.`));
260
264
  }, totalTimeout);
261
265
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dt-common-device",
3
- "version": "13.10.4",
3
+ "version": "13.10.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [