dt-common-device 13.0.16 → 13.0.17
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/dist/config/config.js +41 -0
- package/dist/index.js +1 -1
- package/dist/notificationQueue/index.d.ts +1 -0
- package/dist/notificationQueue/index.js +1 -0
- package/dist/notificationQueue/interfaces/INotificationQueue.d.ts +12 -0
- package/dist/notificationQueue/interfaces/INotificationQueue.js +2 -0
- package/dist/notificationQueue/interfaces/index.d.ts +1 -0
- package/dist/notificationQueue/interfaces/index.js +17 -0
- package/dist/notificationQueue/queue.service.d.ts +13 -16
- package/dist/notificationQueue/queue.service.js +164 -68
- package/package.json +1 -1
package/dist/config/config.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -308,5 +341,13 @@ async function shutdown() {
|
|
|
308
341
|
getConfig().LOGGER.error("Failed to stop event subscription", { error });
|
|
309
342
|
}
|
|
310
343
|
}
|
|
344
|
+
// Close notification queue connections
|
|
345
|
+
try {
|
|
346
|
+
const { closeNotificationQueue } = await Promise.resolve().then(() => __importStar(require("../notificationQueue/queue.service")));
|
|
347
|
+
await closeNotificationQueue();
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
getConfig().LOGGER.error("Failed to close notification queue", { error });
|
|
351
|
+
}
|
|
311
352
|
getConfig().LOGGER.info("dt-common-device: Shutdown completed");
|
|
312
353
|
}
|
package/dist/index.js
CHANGED
|
@@ -65,5 +65,5 @@ __exportStar(require("./webhookQueue"), exports);
|
|
|
65
65
|
__exportStar(require("./copilotQueue"), exports);
|
|
66
66
|
//Export Service Queue
|
|
67
67
|
__exportStar(require("./serviceQueue"), exports);
|
|
68
|
-
// Export Notification Queue (
|
|
68
|
+
// Export Notification Queue (BullMQ for guaranteed delivery)
|
|
69
69
|
__exportStar(require("./notificationQueue"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./INotificationQueue";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./INotificationQueue"), exports);
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
userId: string;
|
|
3
|
-
orgId?: string;
|
|
4
|
-
propertyId?: string;
|
|
5
|
-
data?: any;
|
|
6
|
-
title?: string;
|
|
7
|
-
message?: string;
|
|
8
|
-
url?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface NotificationQueueOptions {
|
|
11
|
-
channel?: string;
|
|
12
|
-
}
|
|
1
|
+
import { INotificationQueuePayload } from "./interfaces";
|
|
13
2
|
/**
|
|
14
|
-
*
|
|
3
|
+
* Enqueue an event payload to BullMQ queue for guaranteed delivery.
|
|
4
|
+
* Jobs are processed in parallel across different userIds but maintain FIFO order per userId.
|
|
5
|
+
* This replaces the previous Redis pub/sub implementation.
|
|
15
6
|
*/
|
|
16
|
-
export declare function enqueueEvent(event:
|
|
7
|
+
export declare function enqueueEvent(event: INotificationQueuePayload): Promise<void>;
|
|
17
8
|
/**
|
|
18
|
-
* Start a
|
|
9
|
+
* Start a BullMQ worker to process notification events.
|
|
10
|
+
* Jobs are processed in parallel across different userIds but maintain FIFO order per userId.
|
|
19
11
|
* The `handler` is responsible for business logic (e.g. calling processEvent()).
|
|
12
|
+
* This replaces the previous Redis pub/sub consumer implementation.
|
|
20
13
|
*/
|
|
21
|
-
export declare function startConsumer(handler: (payload:
|
|
14
|
+
export declare function startConsumer(handler: (payload: INotificationQueuePayload) => Promise<void>): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Gracefully close the worker and queue connections
|
|
17
|
+
*/
|
|
18
|
+
export declare function closeNotificationQueue(): Promise<void>;
|
|
@@ -1,96 +1,192 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.enqueueEvent = enqueueEvent;
|
|
4
7
|
exports.startConsumer = startConsumer;
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
+
exports.closeNotificationQueue = closeNotificationQueue;
|
|
9
|
+
const bullmq_1 = require("bullmq");
|
|
10
|
+
const config_1 = require("../config/config");
|
|
11
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
12
|
+
const QUEUE_NAME = "notification:events";
|
|
13
|
+
// Singleton instances
|
|
14
|
+
let notificationQueue = null;
|
|
15
|
+
let notificationWorker = null;
|
|
16
|
+
let queueEvents = null;
|
|
17
|
+
let redisConnection = null;
|
|
18
|
+
// Track active jobs per userId to maintain FIFO order per user
|
|
19
|
+
// while allowing parallel processing across different users
|
|
20
|
+
const activeJobsPerUser = new Map();
|
|
8
21
|
/**
|
|
9
|
-
*
|
|
22
|
+
* Get or create Redis connection for BullMQ
|
|
10
23
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
function getRedisConnection() {
|
|
25
|
+
if (!redisConnection) {
|
|
26
|
+
const { host, port } = (0, config_1.getRedisDbHostAndPort)();
|
|
27
|
+
redisConnection = new ioredis_1.default({
|
|
28
|
+
host,
|
|
29
|
+
port,
|
|
30
|
+
maxRetriesPerRequest: null,
|
|
31
|
+
enableReadyCheck: false,
|
|
32
|
+
});
|
|
33
|
+
redisConnection.on("error", (error) => {
|
|
34
|
+
(0, config_1.getConfig)().LOGGER.error("Redis connection error for BullMQ", { error });
|
|
35
|
+
});
|
|
36
|
+
redisConnection.on("connect", () => {
|
|
37
|
+
(0, config_1.getConfig)().LOGGER.info("Redis connected for BullMQ");
|
|
23
38
|
});
|
|
24
39
|
}
|
|
40
|
+
return redisConnection;
|
|
25
41
|
}
|
|
26
42
|
/**
|
|
27
|
-
*
|
|
28
|
-
* The `handler` is responsible for business logic (e.g. calling processEvent()).
|
|
43
|
+
* Get or create the notification queue instance
|
|
29
44
|
*/
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
function getQueue() {
|
|
46
|
+
if (!notificationQueue) {
|
|
47
|
+
const connection = getRedisConnection();
|
|
48
|
+
notificationQueue = new bullmq_1.Queue(QUEUE_NAME, {
|
|
49
|
+
connection,
|
|
50
|
+
defaultJobOptions: {
|
|
51
|
+
backoff: {
|
|
52
|
+
type: "exponential",
|
|
53
|
+
delay: 2000,
|
|
54
|
+
},
|
|
55
|
+
removeOnComplete: { age: 5 * 60 }, // Keep completed jobs for 5 minutes
|
|
56
|
+
removeOnFail: { age: 5 * 60 }, // Remove failed jobs after 5 minutes
|
|
57
|
+
attempts: 1, // Only try once
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
// Set up queue event listeners for monitoring
|
|
61
|
+
queueEvents = new bullmq_1.QueueEvents(QUEUE_NAME, { connection });
|
|
62
|
+
queueEvents.on("completed", ({ jobId }) => {
|
|
63
|
+
(0, config_1.getConfig)().LOGGER.info(`Notification job ${jobId} completed`);
|
|
64
|
+
});
|
|
65
|
+
queueEvents.on("failed", ({ jobId, failedReason }) => {
|
|
66
|
+
(0, config_1.getConfig)().LOGGER.error(`Notification job ${jobId} failed`, {
|
|
67
|
+
failedReason,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
35
70
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
return notificationQueue;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Enqueue an event payload to BullMQ queue for guaranteed delivery.
|
|
75
|
+
* Jobs are processed in parallel across different userIds but maintain FIFO order per userId.
|
|
76
|
+
* This replaces the previous Redis pub/sub implementation.
|
|
77
|
+
*/
|
|
78
|
+
async function enqueueEvent(event) {
|
|
79
|
+
const queue = getQueue();
|
|
40
80
|
try {
|
|
41
|
-
|
|
81
|
+
// Use userId as part of jobId to ensure proper ordering per user
|
|
82
|
+
await queue.add("notification-event", event, {
|
|
83
|
+
jobId: `${event.userId}-${Date.now()}-${Math.random()
|
|
84
|
+
.toString(36)
|
|
85
|
+
.substr(2, 9)}`,
|
|
86
|
+
});
|
|
87
|
+
(0, config_1.getConfig)().LOGGER.info("Notification event enqueued successfully", {
|
|
88
|
+
userId: event.userId,
|
|
89
|
+
});
|
|
42
90
|
}
|
|
43
91
|
catch (error) {
|
|
44
|
-
|
|
45
|
-
console.error("Failed to connect Redis subscriber for notifications", {
|
|
92
|
+
(0, config_1.getConfig)().LOGGER.error("Failed to enqueue notification event", {
|
|
46
93
|
error,
|
|
94
|
+
event,
|
|
47
95
|
});
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Start a BullMQ worker to process notification events.
|
|
101
|
+
* Jobs are processed in parallel across different userIds but maintain FIFO order per userId.
|
|
102
|
+
* The `handler` is responsible for business logic (e.g. calling processEvent()).
|
|
103
|
+
* This replaces the previous Redis pub/sub consumer implementation.
|
|
104
|
+
*/
|
|
105
|
+
async function startConsumer(handler) {
|
|
106
|
+
if (notificationWorker) {
|
|
48
107
|
return;
|
|
49
108
|
}
|
|
50
|
-
|
|
51
|
-
// eslint-disable-next-line no-console
|
|
52
|
-
console.error("Redis notification subscriber error", { error });
|
|
53
|
-
});
|
|
109
|
+
const connection = getRedisConnection();
|
|
54
110
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// eslint-disable-next-line no-console
|
|
69
|
-
console.error("Failed to parse pub/sub notification payload", {
|
|
70
|
-
error,
|
|
71
|
-
message,
|
|
72
|
-
channel: subscribedChannel,
|
|
73
|
-
});
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
await handler(payload);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
// eslint-disable-next-line no-console
|
|
81
|
-
console.error("Failed to process pub/sub notification event", {
|
|
82
|
-
error,
|
|
83
|
-
payload,
|
|
84
|
-
channel: subscribedChannel,
|
|
85
|
-
});
|
|
111
|
+
notificationWorker = new bullmq_1.Worker(QUEUE_NAME, async (job) => {
|
|
112
|
+
const payload = job.data;
|
|
113
|
+
const userId = payload.userId;
|
|
114
|
+
// Wait for any existing job for this userId to complete (FIFO per user)
|
|
115
|
+
const previousJob = activeJobsPerUser.get(userId);
|
|
116
|
+
if (previousJob) {
|
|
117
|
+
try {
|
|
118
|
+
await previousJob;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// Previous job failed, but we still want to process this one
|
|
122
|
+
(0, config_1.getConfig)().LOGGER.warn(`Previous job for userId ${userId} failed, continuing with next job`, { error });
|
|
123
|
+
}
|
|
86
124
|
}
|
|
125
|
+
// Create the promise for this job
|
|
126
|
+
const jobPromise = (async () => {
|
|
127
|
+
try {
|
|
128
|
+
await handler(payload);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// Re-throw to let BullMQ handle retries
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
// Remove from active jobs when done
|
|
136
|
+
// Since only one job per userId runs at a time, we can safely delete by userId
|
|
137
|
+
activeJobsPerUser.delete(userId);
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
// Store the promise for this userId
|
|
141
|
+
activeJobsPerUser.set(userId, jobPromise);
|
|
142
|
+
// Wait for this job to complete
|
|
143
|
+
await jobPromise;
|
|
144
|
+
}, {
|
|
145
|
+
connection,
|
|
146
|
+
removeOnComplete: { age: 5 * 60 }, // Remove after 5 minutes
|
|
147
|
+
removeOnFail: { age: 5 * 60 }, // Remove failed jobs after 5 minutes
|
|
148
|
+
});
|
|
149
|
+
notificationWorker.on("completed", (job) => {
|
|
150
|
+
(0, config_1.getConfig)().LOGGER.info(`Notification job ${job.id} completed`);
|
|
151
|
+
});
|
|
152
|
+
notificationWorker.on("failed", (job, err) => {
|
|
153
|
+
(0, config_1.getConfig)().LOGGER.error(`Notification job ${job?.id} failed`, {
|
|
154
|
+
error: err,
|
|
155
|
+
attemptsMade: job?.attemptsMade,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
notificationWorker.on("error", (error) => {
|
|
159
|
+
(0, config_1.getConfig)().LOGGER.error("Notification worker error", { error });
|
|
87
160
|
});
|
|
88
161
|
}
|
|
89
162
|
catch (error) {
|
|
90
|
-
|
|
91
|
-
console.error("Error while subscribing to notification channel", {
|
|
163
|
+
(0, config_1.getConfig)().LOGGER.error("Failed to start notification worker", {
|
|
92
164
|
error,
|
|
93
|
-
channel,
|
|
94
165
|
});
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Gracefully close the worker and queue connections
|
|
171
|
+
*/
|
|
172
|
+
async function closeNotificationQueue() {
|
|
173
|
+
const promises = [];
|
|
174
|
+
if (notificationWorker) {
|
|
175
|
+
promises.push(notificationWorker.close());
|
|
176
|
+
notificationWorker = null;
|
|
177
|
+
}
|
|
178
|
+
if (notificationQueue) {
|
|
179
|
+
promises.push(notificationQueue.close());
|
|
180
|
+
notificationQueue = null;
|
|
181
|
+
}
|
|
182
|
+
if (queueEvents) {
|
|
183
|
+
promises.push(queueEvents.close());
|
|
184
|
+
queueEvents = null;
|
|
185
|
+
}
|
|
186
|
+
if (redisConnection) {
|
|
187
|
+
promises.push(redisConnection.quit().then(() => undefined));
|
|
188
|
+
redisConnection = null;
|
|
95
189
|
}
|
|
190
|
+
await Promise.all(promises);
|
|
191
|
+
(0, config_1.getConfig)().LOGGER.info("Notification queue connections closed");
|
|
96
192
|
}
|