dt-common-device 2.0.7 → 3.0.1
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 +352 -98
- 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/interface.d.ts +101 -0
- package/dist/device/cloud/interface.js +3 -0
- package/dist/device/cloud/interfaces/ICloudDeviceService.d.ts +1 -1
- package/dist/device/cloud/interfaces/IDeviceConnectionService.d.ts +7 -0
- package/dist/device/cloud/interfaces/IDeviceConnectionService.js +3 -0
- package/dist/device/cloud/interfaces/IDevicesService.d.ts +9 -0
- package/dist/device/cloud/interfaces/IDevicesService.js +2 -0
- package/dist/device/cloud/interfaces/IRawDevice.d.ts +1 -1
- package/dist/device/cloud/services/Device.service.d.ts +39 -0
- package/dist/device/cloud/services/Device.service.js +9 -0
- package/dist/device/cloud/services/DeviceCloudService.d.ts +42 -0
- package/dist/device/cloud/services/DeviceCloudService.js +59 -0
- package/dist/device/cloud/services/DeviceHub.service.d.ts +3 -0
- package/dist/device/cloud/services/DeviceHub.service.js +6 -0
- package/dist/device/cloud/services/Hub.service.d.ts +25 -0
- package/dist/device/cloud/services/Hub.service.js +9 -0
- package/dist/device/cloud/services/SmartThingsDeviceService.d.ts +38 -0
- package/dist/device/cloud/services/SmartThingsDeviceService.js +52 -0
- package/dist/device/index.d.ts +4 -0
- package/dist/device/index.js +20 -0
- package/dist/device/local/events/EventHandler.js +6 -6
- package/dist/device/local/events/Events.d.ts +12 -33
- package/dist/device/local/events/Events.js +12 -33
- package/dist/device/local/interface.d.ts +0 -0
- package/dist/device/local/interface.js +1 -0
- 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.js +3 -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 +1 -1
- package/dist/device/local/services/DeviceHub.service.d.ts +11 -0
- package/dist/device/local/services/DeviceHub.service.js +40 -0
- 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 +24 -0
- package/dist/queue/entities/HybridHttpQueue.js +241 -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 +17 -0
- package/dist/queue/interfaces/IHybridHttpQueue.js +2 -0
- package/dist/queue/interfaces/IJobResult.d.ts +14 -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 +31 -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 +60 -0
- package/dist/queue/utils/rateLimit.utils.d.ts +10 -0
- package/dist/queue/utils/rateLimit.utils.js +97 -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 +3 -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 +1 -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 +272 -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 +24 -0
- package/src/queue/interfaces/IJobResult.ts +15 -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 +21 -0
- package/src/queue/utils/index.ts +3 -0
- package/src/queue/utils/jobUtils.ts +80 -0
- package/src/queue/utils/queueUtils.ts +91 -0
- package/src/queue/utils/rateLimit.utils.ts +131 -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
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
removeOnComplete: { age: number; count: number };
|
|
12
|
+
removeOnFail: { age: number; count: number };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface QueueMetrics {
|
|
16
|
+
totalJobs: number;
|
|
17
|
+
completedJobs: number;
|
|
18
|
+
failedJobs: number;
|
|
19
|
+
pendingJobs: number;
|
|
20
|
+
activeJobs: number;
|
|
21
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getConfig } from "../../config/config";
|
|
2
|
+
import { getRedisClient } from "../../db/redis";
|
|
3
|
+
|
|
4
|
+
export class QueueUtils {
|
|
5
|
+
static getQueueKey(
|
|
6
|
+
microservice: string,
|
|
7
|
+
connectionId: string,
|
|
8
|
+
provider: string
|
|
9
|
+
): string {
|
|
10
|
+
return `${microservice}_${provider}_${connectionId}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static getOrCreateQueue(queueKey: string, queues: Map<string, any>): any {
|
|
14
|
+
return (
|
|
15
|
+
queues.get(queueKey) ??
|
|
16
|
+
queues
|
|
17
|
+
.set(
|
|
18
|
+
queueKey,
|
|
19
|
+
new (require("bullmq").Queue)(queueKey, {
|
|
20
|
+
connection: getRedisClient(),
|
|
21
|
+
})
|
|
22
|
+
)
|
|
23
|
+
.get(queueKey)!
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static getOrCreateWorker(
|
|
28
|
+
queueKey: string,
|
|
29
|
+
workers: Map<string, any>,
|
|
30
|
+
processFunction: (job: any) => Promise<any>,
|
|
31
|
+
jobResults: Map<string, any>
|
|
32
|
+
): void {
|
|
33
|
+
if (workers.has(queueKey)) return;
|
|
34
|
+
|
|
35
|
+
const { Worker } = require("bullmq");
|
|
36
|
+
const worker = new Worker(queueKey, processFunction, {
|
|
37
|
+
connection: getRedisClient(),
|
|
38
|
+
concurrency: 1,
|
|
39
|
+
removeOnComplete: { age: 300, count: 1 },
|
|
40
|
+
removeOnFail: { age: 300, count: 1 },
|
|
41
|
+
lockDuration: 300000,
|
|
42
|
+
stalledInterval: 60000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Simplified event handlers
|
|
46
|
+
worker.on("completed", (job: any) => {
|
|
47
|
+
getConfig().LOGGER.info(
|
|
48
|
+
`HTTP request completed: ${job.id} [${queueKey}]`
|
|
49
|
+
);
|
|
50
|
+
job.returnvalue
|
|
51
|
+
.then((result: any) =>
|
|
52
|
+
jobResults.set(job.id!, {
|
|
53
|
+
result,
|
|
54
|
+
resolved: true,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
.catch((error: any) =>
|
|
59
|
+
jobResults.set(job.id!, {
|
|
60
|
+
error: error.message,
|
|
61
|
+
resolved: true,
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
worker.on("failed", (job: any, err: any) => {
|
|
68
|
+
getConfig().LOGGER.error(
|
|
69
|
+
`HTTP request failed: ${job?.id} [${queueKey}], Error: ${err.message}`
|
|
70
|
+
);
|
|
71
|
+
jobResults.set(job!.id!, {
|
|
72
|
+
error: err.message,
|
|
73
|
+
resolved: true,
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
worker.on("error", (err: any) =>
|
|
79
|
+
getConfig().LOGGER.error(`Worker error for ${queueKey}: ${err.message}`)
|
|
80
|
+
);
|
|
81
|
+
worker.on("stalled", (jobId: string) =>
|
|
82
|
+
getConfig().LOGGER.warn(`Job ${jobId} stalled in worker ${queueKey}`)
|
|
83
|
+
);
|
|
84
|
+
worker.on("active", (job: any) =>
|
|
85
|
+
getConfig().LOGGER.info(`HTTP request started: ${job.id} [${queueKey}]`)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
workers.set(queueKey, worker);
|
|
89
|
+
getConfig().LOGGER.info(`Worker initialized for queue: ${queueKey}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { IRateLimitConfig } from "../interfaces";
|
|
2
|
+
import { getRedisClient } from "../../db/redis";
|
|
3
|
+
import { getConfig } from "../../config/config";
|
|
4
|
+
|
|
5
|
+
export class RateLimitUtils {
|
|
6
|
+
private static redisClient = getRedisClient();
|
|
7
|
+
|
|
8
|
+
static async checkRateLimit(
|
|
9
|
+
connectionId: string,
|
|
10
|
+
provider: string,
|
|
11
|
+
rateLimitConfigs: Map<string, IRateLimitConfig>
|
|
12
|
+
): Promise<boolean> {
|
|
13
|
+
const config = rateLimitConfigs.get(provider);
|
|
14
|
+
if (!config) {
|
|
15
|
+
getConfig().LOGGER.warn(
|
|
16
|
+
`No rate limit config found for provider: ${provider}`
|
|
17
|
+
);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const key = `rate_limit:${provider}:${connectionId}`;
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const windowStart = now - config.windowMs;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const data = await this.redisClient.get(key);
|
|
27
|
+
const requests = data
|
|
28
|
+
? JSON.parse(data).filter((t: number) => t > windowStart)
|
|
29
|
+
: [];
|
|
30
|
+
|
|
31
|
+
if (requests.length >= config.maxRequests) return false;
|
|
32
|
+
|
|
33
|
+
requests.push(now);
|
|
34
|
+
await this.redisClient.setex(
|
|
35
|
+
key,
|
|
36
|
+
Math.ceil(config.windowMs / 1000),
|
|
37
|
+
JSON.stringify(requests)
|
|
38
|
+
);
|
|
39
|
+
return true;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
getConfig().LOGGER.error(`Rate limit check error: ${error}`);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static initializeRateLimitConfigs(): Map<string, IRateLimitConfig> {
|
|
47
|
+
const configs = new Map<string, IRateLimitConfig>();
|
|
48
|
+
|
|
49
|
+
// Configure rate limits for different providers
|
|
50
|
+
configs.set("Sensibo", {
|
|
51
|
+
maxRequests: 5,
|
|
52
|
+
windowMs: 60000,
|
|
53
|
+
provider: "Sensibo",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return configs;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async isRateLimitAllowed(
|
|
60
|
+
connectionId: string,
|
|
61
|
+
provider: string,
|
|
62
|
+
rateLimitConfigs: Map<string, IRateLimitConfig>
|
|
63
|
+
): Promise<boolean> {
|
|
64
|
+
const config = rateLimitConfigs.get(provider);
|
|
65
|
+
if (!config) {
|
|
66
|
+
getConfig().LOGGER.warn(
|
|
67
|
+
`No rate limit config found for provider: ${provider}`
|
|
68
|
+
);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const key = `rate_limit:${provider}:${connectionId}`;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const windowStart = now - config.windowMs;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const data = await this.redisClient.get(key);
|
|
78
|
+
const requests = data
|
|
79
|
+
? JSON.parse(data).filter((t: number) => t > windowStart)
|
|
80
|
+
: [];
|
|
81
|
+
|
|
82
|
+
return requests.length < config.maxRequests;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
getConfig().LOGGER.error(`Rate limit check error: ${error}`);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
static async recordRequest(
|
|
89
|
+
connectionId: string,
|
|
90
|
+
provider: string
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const config = this.getRateLimitConfig(provider);
|
|
93
|
+
if (!config) return;
|
|
94
|
+
|
|
95
|
+
const key = `rate_limit:${provider}:${connectionId}`;
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const windowStart = now - config.windowMs;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const data = await this.redisClient.get(key);
|
|
101
|
+
const requests = data
|
|
102
|
+
? JSON.parse(data).filter((t: number) => t > windowStart)
|
|
103
|
+
: [];
|
|
104
|
+
|
|
105
|
+
requests.push(now);
|
|
106
|
+
await this.redisClient.setex(
|
|
107
|
+
key,
|
|
108
|
+
Math.ceil(config.windowMs / 1000),
|
|
109
|
+
JSON.stringify(requests)
|
|
110
|
+
);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
getConfig().LOGGER.error(`Rate limit record error: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static async getRawRequestTimestamps(key: string): Promise<number[]> {
|
|
117
|
+
try {
|
|
118
|
+
const data = await this.redisClient.get(key);
|
|
119
|
+
return data ? JSON.parse(data) : [];
|
|
120
|
+
} catch (error) {
|
|
121
|
+
getConfig().LOGGER.error(
|
|
122
|
+
`Error fetching raw request timestamps: ${error}`
|
|
123
|
+
);
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static getRateLimitConfig(provider: string): IRateLimitConfig | undefined {
|
|
129
|
+
return this.initializeRateLimitConfigs().get(provider);
|
|
130
|
+
}
|
|
131
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# AlertBuilder - Builder Pattern Implementation
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The `AlertBuilder` is a TypeScript implementation of the Builder design pattern, specifically designed to help construct `CreateAlertData` objects with a fluent interface. This is a standard and widely-used design pattern in TypeScript for creating complex objects with optional parameters.
|
|
6
|
-
|
|
7
|
-
## Why Use the Builder Pattern?
|
|
8
|
-
|
|
9
|
-
The Builder pattern is particularly useful for:
|
|
10
|
-
|
|
11
|
-
1. **Complex Object Construction**: When creating objects with many optional parameters
|
|
12
|
-
2. **Fluent Interface**: Provides a readable, chainable API
|
|
13
|
-
3. **Validation**: Ensures required fields are provided before object creation
|
|
14
|
-
4. **Default Values**: Automatically sets sensible defaults
|
|
15
|
-
5. **Type Safety**: Provides compile-time type checking
|
|
16
|
-
|
|
17
|
-
## Usage Examples
|
|
18
|
-
|
|
19
|
-
### Basic Usage
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { AlertBuilder } from "./AlertBuilder";
|
|
23
|
-
import { AlertCategory, AlertSeverity, EntityType } from "../../../types/alert.types";
|
|
24
|
-
|
|
25
|
-
const alertData = new AlertBuilder()
|
|
26
|
-
.setCategory(AlertCategory.OPERATIONS)
|
|
27
|
-
.setPropertyId("prop123")
|
|
28
|
-
.setTitle("Device Offline")
|
|
29
|
-
.setDescription("Device has been offline for more than 5 minutes")
|
|
30
|
-
.setEntityId("device456")
|
|
31
|
-
.setEntityType(EntityType.DEVICE)
|
|
32
|
-
.setSeverity(AlertSeverity.HIGH)
|
|
33
|
-
.setCreatedBy("user789")
|
|
34
|
-
.build();
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Using Static Factory Methods
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
// Create a device-specific alert
|
|
41
|
-
const deviceAlert = AlertBuilder.createDeviceAlert("device123", "prop456")
|
|
42
|
-
.setCategory(AlertCategory.READINESS)
|
|
43
|
-
.setTitle("Device Maintenance Required")
|
|
44
|
-
.setDescription("Device firmware update is available")
|
|
45
|
-
.setSeverity(AlertSeverity.MEDIUM)
|
|
46
|
-
.setCreatedBy("system")
|
|
47
|
-
.build();
|
|
48
|
-
|
|
49
|
-
// Create a hub-specific alert
|
|
50
|
-
const hubAlert = AlertBuilder.createHubAlert("hub789", "prop202")
|
|
51
|
-
.setCategory(AlertCategory.OPERATIONS)
|
|
52
|
-
.setTitle("Hub Connection Lost")
|
|
53
|
-
.setDescription("Hub has lost connection to the network")
|
|
54
|
-
.setSeverity(AlertSeverity.CRITICAL)
|
|
55
|
-
.setCreatedBy("network-monitor")
|
|
56
|
-
.build();
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Using Predefined Alert Types
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// Security alert with CRITICAL severity
|
|
63
|
-
const securityAlert = AlertBuilder.createSecurityAlert()
|
|
64
|
-
.setPropertyId("prop789")
|
|
65
|
-
.setTitle("Unauthorized Access Attempt")
|
|
66
|
-
.setDescription("Multiple failed login attempts detected")
|
|
67
|
-
.setEntityId("user123")
|
|
68
|
-
.setEntityType(EntityType.USER)
|
|
69
|
-
.setCreatedBy("security-system")
|
|
70
|
-
.build();
|
|
71
|
-
|
|
72
|
-
// Energy alert with LOW severity
|
|
73
|
-
const energyAlert = AlertBuilder.createEnergyAlert()
|
|
74
|
-
.setPropertyId("prop101")
|
|
75
|
-
.setTitle("High Energy Consumption")
|
|
76
|
-
.setDescription("Energy usage is 20% above normal levels")
|
|
77
|
-
.setEntityId("zone456")
|
|
78
|
-
.setEntityType(EntityType.COLLECTION)
|
|
79
|
-
.setCreatedBy("energy-monitor")
|
|
80
|
-
.build();
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Reusing Builder Instances
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
const builder = new AlertBuilder()
|
|
87
|
-
.setPropertyId("prop404")
|
|
88
|
-
.setCreatedBy("system");
|
|
89
|
-
|
|
90
|
-
const alert1 = builder
|
|
91
|
-
.setCategory(AlertCategory.OPERATIONS)
|
|
92
|
-
.setTitle("Device Temperature High")
|
|
93
|
-
.setDescription("Device temperature exceeds normal operating range")
|
|
94
|
-
.setEntityId("device789")
|
|
95
|
-
.setEntityType(EntityType.DEVICE)
|
|
96
|
-
.setSeverity(AlertSeverity.HIGH)
|
|
97
|
-
.build();
|
|
98
|
-
|
|
99
|
-
const alert2 = builder
|
|
100
|
-
.reset()
|
|
101
|
-
.setPropertyId("prop404")
|
|
102
|
-
.setCategory(AlertCategory.ENERGY)
|
|
103
|
-
.setTitle("Low Battery Warning")
|
|
104
|
-
.setDescription("Device battery level is below 20%")
|
|
105
|
-
.setEntityId("device789")
|
|
106
|
-
.setEntityType(EntityType.DEVICE)
|
|
107
|
-
.setSeverity(AlertSeverity.MEDIUM)
|
|
108
|
-
.setCreatedBy("system")
|
|
109
|
-
.build();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Available Methods
|
|
113
|
-
|
|
114
|
-
### Instance Methods
|
|
115
|
-
|
|
116
|
-
- `setCategory(category: AlertCategory)`: Sets the alert category
|
|
117
|
-
- `setPropertyId(propertyId: string)`: Sets the property ID (required)
|
|
118
|
-
- `setTitle(title: string)`: Sets the alert title (required)
|
|
119
|
-
- `setDescription(description: string)`: Sets the alert description (required)
|
|
120
|
-
- `setEntityId(entityId?: string)`: Sets the entity ID (optional)
|
|
121
|
-
- `setEntityType(entityType: EntityType)`: Sets the entity type (required)
|
|
122
|
-
- `setSeverity(severity?: AlertSeverity)`: Sets the alert severity (optional, defaults to MEDIUM)
|
|
123
|
-
- `setCreatedBy(createdBy?: string)`: Sets the user who created the alert (optional)
|
|
124
|
-
- `setSnoozeUntil(snoozeUntil?: Date)`: Sets the snooze until date (optional)
|
|
125
|
-
- `build()`: Builds and returns the CreateAlertData object
|
|
126
|
-
- `reset()`: Resets the builder to its initial state
|
|
127
|
-
|
|
128
|
-
### Static Factory Methods
|
|
129
|
-
|
|
130
|
-
- `createReadinessAlert()`: Creates a builder with READINESS category and MEDIUM severity
|
|
131
|
-
- `createOperationsAlert()`: Creates a builder with OPERATIONS category and HIGH severity
|
|
132
|
-
- `createSecurityAlert()`: Creates a builder with SECURITY category and CRITICAL severity
|
|
133
|
-
- `createEnergyAlert()`: Creates a builder with ENERGY category and LOW severity
|
|
134
|
-
- `createDeviceAlert(deviceId: string, propertyId: string)`: Creates a device-specific alert builder
|
|
135
|
-
- `createHubAlert(hubId: string, propertyId: string)`: Creates a hub-specific alert builder
|
|
136
|
-
|
|
137
|
-
## Validation
|
|
138
|
-
|
|
139
|
-
The builder automatically validates that all required fields are present when `build()` is called. Required fields are:
|
|
140
|
-
|
|
141
|
-
- `category`
|
|
142
|
-
- `propertyId`
|
|
143
|
-
- `title`
|
|
144
|
-
- `description`
|
|
145
|
-
- `entityType`
|
|
146
|
-
|
|
147
|
-
If any required field is missing, an error will be thrown with details about which fields are missing.
|
|
148
|
-
|
|
149
|
-
## Default Values
|
|
150
|
-
|
|
151
|
-
- `severity`: Defaults to `AlertSeverity.MEDIUM` if not specified
|
|
152
|
-
|
|
153
|
-
## Error Handling
|
|
154
|
-
|
|
155
|
-
The builder includes comprehensive error handling:
|
|
156
|
-
|
|
157
|
-
- Validates required fields before building
|
|
158
|
-
- Trims whitespace from string inputs
|
|
159
|
-
- Validates that propertyId and title are not empty strings
|
|
160
|
-
- Provides clear error messages for missing or invalid data
|
|
161
|
-
|
|
162
|
-
## Design Pattern Benefits
|
|
163
|
-
|
|
164
|
-
This implementation follows the Builder pattern which provides:
|
|
165
|
-
|
|
166
|
-
1. **Separation of Concerns**: Object construction is separated from object representation
|
|
167
|
-
2. **Fluent Interface**: Method chaining for better readability
|
|
168
|
-
3. **Immutability**: Each method returns a new builder instance
|
|
169
|
-
4. **Validation**: Built-in validation before object creation
|
|
170
|
-
5. **Flexibility**: Easy to add new fields or modify existing ones
|
|
171
|
-
6. **Type Safety**: Full TypeScript support with compile-time checking
|
|
172
|
-
|
|
173
|
-
This is indeed a standard design pattern in TypeScript and is widely used in enterprise applications for constructing complex objects with many optional parameters.
|
package/src/types/index.ts
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|