dt-common-device 2.0.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +321 -99
- package/dist/alerts/Alert.model.d.ts +28 -0
- package/dist/alerts/Alert.model.js +222 -0
- package/dist/alerts/Alert.repository.d.ts +106 -0
- package/dist/alerts/Alert.repository.js +374 -0
- package/dist/alerts/Alert.service.d.ts +137 -0
- package/dist/alerts/Alert.service.js +476 -0
- package/dist/alerts/AlertBuilder.d.ts +87 -0
- package/dist/alerts/AlertBuilder.example.d.ts +11 -0
- package/dist/alerts/AlertBuilder.example.js +117 -0
- package/dist/alerts/AlertBuilder.js +185 -0
- package/dist/alerts/AlertService.example.d.ts +55 -0
- package/dist/alerts/AlertService.example.js +148 -0
- package/dist/alerts/alert.types.d.ts +57 -0
- package/dist/alerts/alert.types.js +22 -0
- package/dist/alerts/index.d.ts +3 -0
- package/dist/alerts/index.js +19 -0
- package/dist/config/config.d.ts +4 -4
- package/dist/config/config.js +3 -3
- package/dist/config/config.types.d.ts +19 -0
- package/dist/config/config.types.js +2 -0
- package/dist/connection/Connection.repository.d.ts +8 -0
- package/dist/connection/Connection.repository.js +92 -0
- package/dist/connection/Connection.service.d.ts +8 -0
- package/dist/connection/Connection.service.js +32 -0
- package/dist/connection/IConnection.d.ts +26 -0
- package/dist/connection/IConnection.js +14 -0
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.js +18 -0
- package/dist/device/cloud/entities/CloudDevice.d.ts +2 -2
- package/dist/device/cloud/entities/CloudDeviceService.d.ts +1 -1
- package/dist/device/cloud/entities/DeviceFactory.d.ts +1 -1
- package/dist/device/cloud/entities/DeviceFactory.js +1 -1
- package/dist/device/cloud/interfaces/ICloudDeviceService.d.ts +1 -1
- package/dist/device/cloud/interfaces/IRawDevice.d.ts +1 -1
- package/dist/device/local/interfaces/index.d.ts +2 -3
- package/dist/device/local/interfaces/index.js +2 -3
- package/dist/device/local/repository/Device.repository.d.ts +2 -0
- package/dist/device/local/repository/Device.repository.js +22 -3
- package/dist/device/local/repository/Hub.repository.js +4 -4
- package/dist/device/local/repository/Schedule.repository.js +2 -2
- package/dist/device/local/services/Device.service.d.ts +2 -2
- package/dist/device/local/services/Device.service.js +3 -1
- package/dist/device/local/services/index.d.ts +0 -4
- package/dist/device/local/services/index.js +0 -4
- package/dist/events/BaseEventHandler.d.ts +2 -2
- package/dist/events/BaseEventHandler.js +2 -2
- package/dist/events/BaseEventTransformer.d.ts +1 -1
- package/dist/events/BaseEventTransformer.js +1 -1
- package/dist/events/DeviceEventHandler.d.ts +1 -1
- package/dist/events/DeviceEventHandler.js +2 -2
- package/dist/events/EventHandler.js +1 -1
- package/dist/events/EventHandlerOrchestrator.js +1 -1
- package/dist/events/EventProcessingService.js +1 -1
- package/dist/events/InternalEventSubscription.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +16 -13
- package/dist/issues/Issue.model.d.ts +28 -0
- package/dist/issues/Issue.model.js +260 -0
- package/dist/issues/Issue.repository.d.ts +113 -0
- package/dist/issues/Issue.repository.js +401 -0
- package/dist/issues/Issue.service.d.ts +168 -0
- package/dist/issues/Issue.service.js +642 -0
- package/dist/issues/IssueBuilder.d.ts +109 -0
- package/dist/issues/IssueBuilder.example.d.ts +16 -0
- package/dist/issues/IssueBuilder.example.js +196 -0
- package/dist/issues/IssueBuilder.js +237 -0
- package/dist/issues/IssueService.example.d.ts +68 -0
- package/dist/issues/IssueService.example.js +177 -0
- package/dist/issues/index.d.ts +2 -0
- package/dist/issues/index.js +18 -0
- package/dist/issues/issue.types.d.ts +90 -0
- package/dist/issues/issue.types.js +40 -0
- package/dist/property/IProperty.d.ts +29 -0
- package/dist/property/IProperty.js +2 -0
- package/dist/property/Property.repository.d.ts +8 -0
- package/dist/property/Property.repository.js +95 -0
- package/dist/property/Property.service.d.ts +8 -0
- package/dist/property/Property.service.js +36 -0
- package/dist/property/index.d.ts +2 -0
- package/dist/property/index.js +18 -0
- package/dist/queue/entities/HybridHttpQueue.d.ts +23 -0
- package/dist/queue/entities/HybridHttpQueue.js +189 -0
- package/dist/queue/entities/index.d.ts +1 -0
- package/dist/queue/entities/index.js +17 -0
- package/dist/queue/index.d.ts +5 -0
- package/dist/queue/index.js +22 -0
- package/dist/queue/interfaces/IHttpRequestJob.d.ts +9 -0
- package/dist/queue/interfaces/IHttpRequestJob.js +2 -0
- package/dist/queue/interfaces/IHybridHttpQueue.d.ts +16 -0
- package/dist/queue/interfaces/IHybridHttpQueue.js +2 -0
- package/dist/queue/interfaces/IJobResult.d.ts +6 -0
- package/dist/queue/interfaces/IJobResult.js +2 -0
- package/dist/queue/interfaces/IRateLimitConfig.d.ts +5 -0
- package/dist/queue/interfaces/IRateLimitConfig.js +2 -0
- package/dist/queue/interfaces/index.d.ts +4 -0
- package/dist/queue/interfaces/index.js +20 -0
- package/dist/queue/services/QueueService.d.ts +19 -0
- package/dist/queue/services/QueueService.js +73 -0
- package/dist/queue/services/index.d.ts +1 -0
- package/dist/queue/services/index.js +17 -0
- package/dist/queue/types/http.types.d.ts +21 -0
- package/dist/queue/types/http.types.js +2 -0
- package/dist/queue/types/index.d.ts +2 -0
- package/dist/queue/types/index.js +18 -0
- package/dist/queue/types/queue.types.d.ts +35 -0
- package/dist/queue/types/queue.types.js +2 -0
- package/dist/queue/utils/index.d.ts +3 -0
- package/dist/queue/utils/index.js +19 -0
- package/dist/queue/utils/jobUtils.d.ts +10 -0
- package/dist/queue/utils/jobUtils.js +64 -0
- package/dist/queue/utils/queueUtils.d.ts +5 -0
- package/dist/queue/utils/queueUtils.js +59 -0
- package/dist/queue/utils/rateLimit.utils.d.ts +6 -0
- package/dist/queue/utils/rateLimit.utils.js +44 -0
- package/package.json +2 -1
- package/src/{device/local/models → alerts}/Alert.model.ts +1 -1
- package/src/{device/local/repository → alerts}/Alert.repository.ts +2 -2
- package/src/{device/local/services → alerts}/Alert.service.ts +14 -7
- package/src/{device/local/entities → alerts}/AlertBuilder.example.ts +2 -2
- package/src/{device/local/entities → alerts}/AlertBuilder.ts +14 -8
- package/src/{device/local/services → alerts}/AlertService.example.ts +6 -5
- package/src/{types → alerts}/alert.types.ts +2 -2
- package/src/alerts/index.ts +3 -0
- package/src/config/config.ts +7 -7
- package/src/{types → config}/config.types.ts +1 -1
- package/src/{device/local/repository → connection}/Connection.repository.ts +2 -2
- package/src/{device/local/services → connection}/Connection.service.ts +2 -2
- package/src/connection/index.ts +3 -0
- package/src/device/cloud/entities/CloudDevice.ts +2 -2
- package/src/device/cloud/entities/CloudDeviceService.ts +1 -1
- package/src/device/cloud/entities/DeviceFactory.ts +2 -2
- package/src/device/cloud/interfaces/ICloudDeviceService.ts +1 -1
- package/src/device/cloud/interfaces/IRawDevice.ts +1 -1
- package/src/device/local/interfaces/index.ts +2 -3
- package/src/device/local/repository/Device.repository.ts +29 -3
- package/src/device/local/repository/Hub.repository.ts +4 -4
- package/src/device/local/repository/Schedule.repository.ts +2 -2
- package/src/device/local/services/Device.service.ts +5 -1
- package/src/device/local/services/index.ts +0 -4
- package/{TROUBLESHOOTING.md → src/docs/TROUBLESHOOTING.md} +2 -2
- package/src/events/BaseEventHandler.ts +3 -3
- package/src/events/BaseEventTransformer.ts +2 -2
- package/src/events/DeviceEventHandler.ts +3 -3
- package/src/events/EventHandler.ts +1 -1
- package/src/events/EventHandlerOrchestrator.ts +2 -2
- package/src/events/EventProcessingService.ts +2 -2
- package/src/events/InternalEventSubscription.ts +2 -2
- package/src/index.ts +19 -13
- package/src/{device/local/models → issues}/Issue.model.ts +1 -1
- package/src/{device/local/repository → issues}/Issue.repository.ts +2 -2
- package/src/{device/local/services → issues}/Issue.service.ts +4 -4
- package/src/{device/local/entities → issues}/IssueBuilder.example.ts +1 -1
- package/src/{device/local/entities → issues}/IssueBuilder.ts +1 -1
- package/src/{device/local/services → issues}/IssueService.example.ts +6 -5
- package/src/issues/index.ts +2 -0
- package/src/{device/local/repository → property}/Property.repository.ts +2 -2
- package/src/{device/local/services → property}/Property.service.ts +1 -1
- package/src/property/index.ts +2 -0
- package/src/queue/entities/HybridHttpQueue.ts +196 -0
- package/src/queue/entities/index.ts +1 -0
- package/src/queue/index.ts +6 -0
- package/src/queue/interfaces/IHttpRequestJob.ts +10 -0
- package/src/queue/interfaces/IHybridHttpQueue.ts +23 -0
- package/src/queue/interfaces/IJobResult.ts +6 -0
- package/src/queue/interfaces/IRateLimitConfig.ts +5 -0
- package/src/queue/interfaces/index.ts +4 -0
- package/src/queue/services/QueueService.ts +39 -0
- package/src/queue/services/index.ts +1 -0
- package/src/queue/types/http.types.ts +22 -0
- package/src/queue/types/index.ts +2 -0
- package/src/queue/types/queue.types.ts +22 -0
- package/src/queue/utils/index.ts +3 -0
- package/src/queue/utils/jobUtils.ts +80 -0
- package/src/queue/utils/queueUtils.ts +90 -0
- package/src/queue/utils/rateLimit.utils.ts +58 -0
- package/tsconfig.json +4 -0
- package/src/device/local/entities/README.md +0 -173
- package/src/device/local/entities/index.ts +0 -2
- package/src/types/index.ts +0 -3
- /package/src/{device/local/interfaces → connection}/IConnection.ts +0 -0
- /package/src/{device/local/models → docs}/Alert.model.md +0 -0
- /package/src/{device/local/models/README.md → docs/Alerts&IssuesModel.md} +0 -0
- /package/src/{device/local/models → docs}/Issue.model.md +0 -0
- /package/{SECURITY.md → src/docs/SECURITY.md} +0 -0
- /package/src/{types → issues}/issue.types.ts +0 -0
- /package/src/{device/local/interfaces → property}/IProperty.ts +0 -0
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Main entry point for dt-common-device
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// DEVICE EXPORTS
|
|
4
4
|
export {
|
|
5
5
|
CloudDevice,
|
|
6
6
|
CloudDeviceService,
|
|
@@ -9,26 +9,32 @@ export {
|
|
|
9
9
|
export {
|
|
10
10
|
LocalDeviceService,
|
|
11
11
|
LocalHubService,
|
|
12
|
-
LocalConnectionService,
|
|
13
|
-
LocalPropertyService,
|
|
14
12
|
LocalScheduleService,
|
|
15
13
|
} from "./device/local/services";
|
|
14
|
+
export * from "./device/local/interfaces";
|
|
15
|
+
export * from "./device/cloud/interfaces";
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
export * from "./
|
|
19
|
-
export * from "./events/interfaces";
|
|
17
|
+
// CONNECTION EXPORTS
|
|
18
|
+
export * from "./connection";
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
// PROPERTY EXPORTS
|
|
21
|
+
export * from "./property";
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
export * from "./device/local/interfaces";
|
|
23
|
+
// EVENTS EXPORTS
|
|
25
24
|
export * from "./events";
|
|
26
25
|
export * from "./events/interfaces";
|
|
27
|
-
// Types exports
|
|
28
|
-
export * from "./types";
|
|
29
26
|
|
|
30
|
-
//
|
|
27
|
+
// ALERTS EXPORTS
|
|
28
|
+
export * from "./alerts";
|
|
29
|
+
|
|
30
|
+
// ISSUES EXPORTS
|
|
31
|
+
export * from "./issues";
|
|
32
|
+
|
|
33
|
+
// REDIS EXPORTS
|
|
31
34
|
export * from "./utils";
|
|
32
35
|
|
|
33
|
-
//
|
|
36
|
+
// QUEUE EXPORTS
|
|
37
|
+
export * from "./queue";
|
|
38
|
+
|
|
39
|
+
// CONFIG EXPORTS
|
|
34
40
|
export { initialize, getConfig, shutdown } from "./config/config";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Service } from "typedi";
|
|
2
|
-
import { IssueModel, IIssueDocument } from "
|
|
2
|
+
import { IssueModel, IIssueDocument } from "./Issue.model";
|
|
3
3
|
import {
|
|
4
4
|
CreateIssueData,
|
|
5
5
|
UpdateIssueData,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
IssuePriority,
|
|
8
8
|
IssuesCategory,
|
|
9
9
|
EntityType,
|
|
10
|
-
} from "
|
|
10
|
+
} from "./issue.types";
|
|
11
11
|
|
|
12
12
|
@Service()
|
|
13
13
|
export class IssueRepository {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Service } from "typedi";
|
|
2
|
-
import { IssueRepository } from "
|
|
3
|
-
import { IssueModel, IIssueDocument } from "
|
|
2
|
+
import { IssueRepository } from "./Issue.repository";
|
|
3
|
+
import { IssueModel, IIssueDocument } from "./Issue.model";
|
|
4
4
|
import {
|
|
5
5
|
CreateIssueData,
|
|
6
6
|
UpdateIssueData,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
IssuePriority,
|
|
10
10
|
IssuesCategory,
|
|
11
11
|
EntityType,
|
|
12
|
-
} from "
|
|
13
|
-
import { IssueBuilder } from "
|
|
12
|
+
} from "./issue.types";
|
|
13
|
+
import { IssueBuilder } from "./IssueBuilder";
|
|
14
14
|
|
|
15
15
|
@Service()
|
|
16
16
|
export class IssueService {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IssueService } from "./Issue.service";
|
|
2
|
-
import { IssueBuilder } from "
|
|
3
|
-
import { IssuesCategory, IssuePriority, EntityType } from "
|
|
2
|
+
import { IssueBuilder } from "./IssueBuilder";
|
|
3
|
+
import { IssuesCategory, IssuePriority, EntityType } from "./issue.types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Example usage of the updated IssueService with IssueBuilder integration
|
|
@@ -291,17 +291,18 @@ export class IssueServiceExample {
|
|
|
291
291
|
category: IssuesCategory.OPERATIONS,
|
|
292
292
|
propertyId: "prop123",
|
|
293
293
|
title: "Legacy Issue",
|
|
294
|
-
description:
|
|
294
|
+
description:
|
|
295
|
+
"This issue was created using the old CreateIssueData format",
|
|
295
296
|
entityId: "device456",
|
|
296
297
|
entityType: EntityType.DEVICE,
|
|
297
298
|
priority: IssuePriority.MEDIUM,
|
|
298
299
|
assignedTo: "tech-support",
|
|
299
300
|
createdBy: "legacy-system",
|
|
300
|
-
dueDate: new Date("2024-01-20")
|
|
301
|
+
dueDate: new Date("2024-01-20"),
|
|
301
302
|
};
|
|
302
303
|
|
|
303
304
|
// This still works with the updated createIssue method
|
|
304
305
|
const issue = await this.issueService.createIssue(issueData);
|
|
305
306
|
return issue;
|
|
306
307
|
}
|
|
307
|
-
}
|
|
308
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getPostgresClient } from "
|
|
1
|
+
import { getPostgresClient } from "../db";
|
|
2
2
|
import { Service } from "typedi";
|
|
3
|
-
import { IProperty, IPropertySettings } from "
|
|
3
|
+
import { IProperty, IPropertySettings } from "./IProperty";
|
|
4
4
|
|
|
5
5
|
@Service()
|
|
6
6
|
export class PropertyRepository {
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { HttpCallOption } from "../types/http.types";
|
|
2
|
+
import { getConfig } from "../../config/config";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import { publishAudit } from "dt-audit-library";
|
|
5
|
+
import { Service } from "typedi";
|
|
6
|
+
import { RateLimitUtils } from "../utils/rateLimit.utils";
|
|
7
|
+
import { JobUtils } from "../utils/jobUtils";
|
|
8
|
+
import { QueueUtils } from "../utils/queueUtils";
|
|
9
|
+
import { IRateLimitConfig, IJobResult, IHttpRequestJob } from "../interfaces";
|
|
10
|
+
|
|
11
|
+
@Service()
|
|
12
|
+
export class HybridHttpQueue {
|
|
13
|
+
private readonly queues = new Map<string, any>();
|
|
14
|
+
private readonly workers = new Map<string, any>();
|
|
15
|
+
private readonly rateLimitConfigs: Map<string, IRateLimitConfig>;
|
|
16
|
+
private readonly jobResults = new Map<string, IJobResult>();
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.rateLimitConfigs = RateLimitUtils.initializeRateLimitConfigs();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async handleRateLimit(job: any): Promise<void> {
|
|
23
|
+
const { connectionId, provider, url, method, options } = job.data;
|
|
24
|
+
const microservice = options.queueOptions?.microservice || "default";
|
|
25
|
+
const isMaxRetries = job.attemptsMade >= 2;
|
|
26
|
+
|
|
27
|
+
await publishAudit({
|
|
28
|
+
eventType: isMaxRetries
|
|
29
|
+
? "http.request.failed"
|
|
30
|
+
: "http.request.rateLimitExceeded",
|
|
31
|
+
properties: {
|
|
32
|
+
connectionId,
|
|
33
|
+
provider,
|
|
34
|
+
endpoint: url,
|
|
35
|
+
method,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
queueId: job.id,
|
|
38
|
+
reason: isMaxRetries ? "max_retries_exceeded" : "rate_limit_exceeded",
|
|
39
|
+
...(!isMaxRetries && {
|
|
40
|
+
retryCount: job.attemptsMade + 1,
|
|
41
|
+
maxRetries: 3,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (isMaxRetries) {
|
|
47
|
+
await this.queues
|
|
48
|
+
.get(QueueUtils.getQueueKey(microservice, connectionId, provider))
|
|
49
|
+
?.obliterate({ force: true });
|
|
50
|
+
// Don't throw error for max retries - just return
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error("Rate limit exceeded");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async processHttpRequest(job: any): Promise<any> {
|
|
58
|
+
const { connectionId, provider, url, method, options } = job.data;
|
|
59
|
+
|
|
60
|
+
// Check rate limit
|
|
61
|
+
if (
|
|
62
|
+
!(await RateLimitUtils.checkRateLimit(
|
|
63
|
+
connectionId,
|
|
64
|
+
provider,
|
|
65
|
+
this.rateLimitConfigs
|
|
66
|
+
))
|
|
67
|
+
) {
|
|
68
|
+
await this.handleRateLimit(job);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
getConfig().LOGGER.info(
|
|
74
|
+
`Executing HTTP request: ${method} ${url} for ${provider}`
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const response = await axios({
|
|
78
|
+
method: method.toLowerCase(),
|
|
79
|
+
url: url,
|
|
80
|
+
headers: options.headers || {},
|
|
81
|
+
timeout: 30000,
|
|
82
|
+
...(options.body && { data: options.body }),
|
|
83
|
+
...(options.params && { params: options.params }),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
getConfig().LOGGER.info(
|
|
87
|
+
`HTTP request successful: ${method} ${url} for ${provider}`
|
|
88
|
+
);
|
|
89
|
+
return response.data;
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
getConfig().LOGGER.error(
|
|
92
|
+
`HTTP request failed ${job.id}: ${error.message}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await publishAudit({
|
|
96
|
+
eventType: "http.request.error",
|
|
97
|
+
properties: {
|
|
98
|
+
connectionId,
|
|
99
|
+
provider,
|
|
100
|
+
endpoint: url,
|
|
101
|
+
method,
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
queueId: job.id,
|
|
104
|
+
reason: "execution_error",
|
|
105
|
+
errorMessage: error.message,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
throw new Error(`HTTP request failed: ${error.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async request(options: {
|
|
114
|
+
method: string;
|
|
115
|
+
url: string;
|
|
116
|
+
body?: any;
|
|
117
|
+
headers?: Record<string, string>;
|
|
118
|
+
queueOptions?: {
|
|
119
|
+
connectionId: string;
|
|
120
|
+
connectionProvider: string;
|
|
121
|
+
microservice: string;
|
|
122
|
+
};
|
|
123
|
+
}): Promise<any> {
|
|
124
|
+
const { method, url, body, headers, queueOptions } = options;
|
|
125
|
+
// Create HttpCallOption object
|
|
126
|
+
const httpCallOption: HttpCallOption = {
|
|
127
|
+
headers,
|
|
128
|
+
body,
|
|
129
|
+
queueOptions,
|
|
130
|
+
};
|
|
131
|
+
// Call handleRequest with the constructed parameters
|
|
132
|
+
return this.handleRequest(url, method, httpCallOption);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async handleRequest(
|
|
136
|
+
url: string,
|
|
137
|
+
method: string,
|
|
138
|
+
options: HttpCallOption
|
|
139
|
+
): Promise<any> {
|
|
140
|
+
const { connectionId, provider, microservice } =
|
|
141
|
+
JobUtils.extractConnectionDetails(options);
|
|
142
|
+
const queueKey = QueueUtils.getQueueKey(
|
|
143
|
+
microservice,
|
|
144
|
+
connectionId,
|
|
145
|
+
provider
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
getConfig().LOGGER.info(
|
|
149
|
+
`Queueing: ${method} ${url} -> ${provider} [${connectionId}]`
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
QueueUtils.getOrCreateWorker(
|
|
153
|
+
queueKey,
|
|
154
|
+
this.workers,
|
|
155
|
+
this.processHttpRequest.bind(this),
|
|
156
|
+
this.jobResults
|
|
157
|
+
);
|
|
158
|
+
const queue = QueueUtils.getOrCreateQueue(queueKey, this.queues);
|
|
159
|
+
|
|
160
|
+
const job = await queue.add(
|
|
161
|
+
"http-request",
|
|
162
|
+
{
|
|
163
|
+
microservice,
|
|
164
|
+
connectionId,
|
|
165
|
+
provider,
|
|
166
|
+
url,
|
|
167
|
+
method,
|
|
168
|
+
options,
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
attempts: 3,
|
|
173
|
+
backoff: { type: "exponential", delay: 5000 },
|
|
174
|
+
removeOnComplete: { age: 300, count: 1 },
|
|
175
|
+
removeOnFail: { age: 300, count: 1 },
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
getConfig().LOGGER.info(`Job ${job.id} queued, waiting for completion...`);
|
|
180
|
+
return JobUtils.waitForJobCompletion(job, queueKey, this.jobResults);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async shutdown(): Promise<void> {
|
|
184
|
+
getConfig().LOGGER.info("Shutting down HTTP queues...");
|
|
185
|
+
|
|
186
|
+
await Promise.all([
|
|
187
|
+
...Array.from(this.workers.values()).map((worker: any) => worker.close()),
|
|
188
|
+
...Array.from(this.queues.values()).map((queue: any) => queue.close()),
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
this.workers.clear();
|
|
192
|
+
this.queues.clear();
|
|
193
|
+
this.jobResults.clear();
|
|
194
|
+
getConfig().LOGGER.info("HTTP queues shutdown complete");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./HybridHttpQueue";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpCallOption } from "../types/http.types";
|
|
2
|
+
|
|
3
|
+
export interface IHybridHttpQueue {
|
|
4
|
+
request(options: {
|
|
5
|
+
method: string;
|
|
6
|
+
url: string;
|
|
7
|
+
body?: any;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
queueOptions?: {
|
|
10
|
+
connectionId: string;
|
|
11
|
+
connectionProvider: string;
|
|
12
|
+
microservice: string;
|
|
13
|
+
};
|
|
14
|
+
}): Promise<any>;
|
|
15
|
+
|
|
16
|
+
handleRequest(
|
|
17
|
+
url: string,
|
|
18
|
+
method: string,
|
|
19
|
+
options: HttpCallOption
|
|
20
|
+
): Promise<any>;
|
|
21
|
+
|
|
22
|
+
shutdown(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Service } from "typedi";
|
|
2
|
+
import { HybridHttpQueue } from "../entities/HybridHttpQueue";
|
|
3
|
+
import { IHybridHttpQueue } from "../interfaces";
|
|
4
|
+
import { HttpCallOption } from "../types/http.types";
|
|
5
|
+
|
|
6
|
+
@Service()
|
|
7
|
+
export class QueueService implements IHybridHttpQueue {
|
|
8
|
+
private readonly hybridQueue: HybridHttpQueue;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.hybridQueue = new HybridHttpQueue();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async request(options: {
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
body?: any;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
queueOptions?: {
|
|
20
|
+
connectionId: string;
|
|
21
|
+
connectionProvider: string;
|
|
22
|
+
microservice: string;
|
|
23
|
+
};
|
|
24
|
+
}): Promise<any> {
|
|
25
|
+
return this.hybridQueue.request(options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handleRequest(
|
|
29
|
+
url: string,
|
|
30
|
+
method: string,
|
|
31
|
+
options: HttpCallOption
|
|
32
|
+
): Promise<any> {
|
|
33
|
+
return this.hybridQueue.handleRequest(url, method, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async shutdown(): Promise<void> {
|
|
37
|
+
return this.hybridQueue.shutdown();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./QueueService";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface HttpCallOption {
|
|
2
|
+
headers?: Record<string, string>;
|
|
3
|
+
body?: any;
|
|
4
|
+
params?: Record<string, any>;
|
|
5
|
+
queueOptions?: {
|
|
6
|
+
connectionId: string;
|
|
7
|
+
connectionProvider: string;
|
|
8
|
+
microservice: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HttpRequestOptions {
|
|
13
|
+
method: string;
|
|
14
|
+
url: string;
|
|
15
|
+
body?: any;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
queueOptions?: {
|
|
18
|
+
connectionId: string;
|
|
19
|
+
connectionProvider: string;
|
|
20
|
+
microservice: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface QueueConfig {
|
|
2
|
+
concurrency: number;
|
|
3
|
+
removeOnComplete: { age: number; count: number };
|
|
4
|
+
removeOnFail: { age: number; count: number };
|
|
5
|
+
lockDuration: number;
|
|
6
|
+
stalledInterval: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface JobOptions {
|
|
10
|
+
attempts: number;
|
|
11
|
+
backoff: { type: string; delay: number };
|
|
12
|
+
removeOnComplete: { age: number; count: number };
|
|
13
|
+
removeOnFail: { age: number; count: number };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface QueueMetrics {
|
|
17
|
+
totalJobs: number;
|
|
18
|
+
completedJobs: number;
|
|
19
|
+
failedJobs: number;
|
|
20
|
+
pendingJobs: number;
|
|
21
|
+
activeJobs: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Job } from "bullmq";
|
|
2
|
+
import { IJobResult } from "../interfaces";
|
|
3
|
+
import { getConfig } from "../../config/config";
|
|
4
|
+
|
|
5
|
+
export class JobUtils {
|
|
6
|
+
static async waitForJobCompletion(
|
|
7
|
+
job: Job,
|
|
8
|
+
queueKey: string,
|
|
9
|
+
jobResults: Map<string, IJobResult>
|
|
10
|
+
): Promise<any> {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let timeoutId: NodeJS.Timeout;
|
|
13
|
+
let checkCount = 0;
|
|
14
|
+
const maxChecks = 600;
|
|
15
|
+
|
|
16
|
+
const checkJob = async () => {
|
|
17
|
+
if (++checkCount >= maxChecks) {
|
|
18
|
+
clearTimeout(timeoutId);
|
|
19
|
+
return reject(new Error("Job timeout: Request took too long"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const state = await job.getState();
|
|
24
|
+
|
|
25
|
+
if (state === "completed") {
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
const memoryResult = jobResults.get(job.id!);
|
|
28
|
+
|
|
29
|
+
if (memoryResult?.resolved) {
|
|
30
|
+
return memoryResult.result !== undefined
|
|
31
|
+
? resolve(memoryResult.result)
|
|
32
|
+
: reject(new Error(memoryResult.error || "Unknown error"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fallback to job result
|
|
36
|
+
resolve(await job.returnvalue);
|
|
37
|
+
} else if (state === "failed") {
|
|
38
|
+
clearTimeout(timeoutId);
|
|
39
|
+
const memoryResult = jobResults.get(job.id!);
|
|
40
|
+
const errorMsg =
|
|
41
|
+
memoryResult?.error ||
|
|
42
|
+
(await job.failedReason) ||
|
|
43
|
+
"Unknown error";
|
|
44
|
+
reject(new Error(`Job failed: ${errorMsg}`));
|
|
45
|
+
} else {
|
|
46
|
+
timeoutId = setTimeout(checkJob, checkCount < 10 ? 50 : 100);
|
|
47
|
+
}
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
reject(new Error(`Error checking job: ${error.message}`));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
checkJob();
|
|
55
|
+
|
|
56
|
+
// Backup timeout
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
reject(new Error("Request timeout: Maximum wait time exceeded"));
|
|
60
|
+
}, 60000);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static extractConnectionDetails(options: any): {
|
|
65
|
+
connectionId: string;
|
|
66
|
+
provider: string;
|
|
67
|
+
microservice: string;
|
|
68
|
+
} {
|
|
69
|
+
const {
|
|
70
|
+
connectionId,
|
|
71
|
+
connectionProvider: provider,
|
|
72
|
+
microservice,
|
|
73
|
+
} = options?.queueOptions ?? {};
|
|
74
|
+
|
|
75
|
+
if (!connectionId) throw new Error("Connection ID not found in options");
|
|
76
|
+
if (!provider) throw new Error("Connection provider not found in options");
|
|
77
|
+
if (!microservice) throw new Error("Microservice not found in options");
|
|
78
|
+
return { connectionId, provider, microservice };
|
|
79
|
+
}
|
|
80
|
+
}
|