dt-common-device 13.10.6 → 13.10.8

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
@@ -1,452 +1,452 @@
1
- # dt-common-device
2
-
3
- A comprehensive TypeScript library for device management, supporting both cloud and local device operations with advanced event handling, queue management, and audit logging.
4
-
5
- ## Installation
6
-
7
- ```sh
8
- npm install dt-common-device
9
- ```
10
-
11
- ## Environment Variables Required
12
-
13
- **The following environment variables are REQUIRED for the library to function:**
14
-
15
- ### AWS Configuration
16
-
17
- - `AWS_SECRET_ACCESS_KEY` — Your AWS secret access key
18
- - `AWS_REGION` — Your AWS region
19
- - `AWS_ACCESS_KEY_ID` — Your AWS access key ID
20
- - `EVENT_BUS_NAME` — Your AWS EventBridge event bus name
21
-
22
- ### Database Configuration
23
-
24
- - `ADMIN_DB_URI` — PostgreSQL database connection URI
25
- - `MONGODB_URI` — MongoDB database connection URI
26
-
27
- ### Redis Configuration
28
-
29
- - `REDIS_HOST` — Redis server host
30
- - `REDIS_PORT` — Redis server port
31
-
32
- ### Audit Logging
33
-
34
- - `POSTHOG_API_KEY` — Your PostHog API key
35
- - `POSTHOG_HOST` — The PostHog host URL
36
-
37
- ### SQS Configuration
38
-
39
- - `SQS_QUEUE_URL` — AWS SQS queue URL (configured during initialization)
40
-
41
- Example `.env` file:
42
-
43
- ```
44
- # AWS Configuration
45
- AWS_SECRET_ACCESS_KEY=your_secret_key
46
- AWS_REGION=us-east-1
47
- AWS_ACCESS_KEY_ID=your_access_key
48
- EVENT_BUS_NAME=your-event-bus
49
-
50
- # Database Configuration
51
- ADMIN_DB_URI=postgres://username:password@host:port/database
52
- MONGODB_URI=mongodb://username:password@host:port/database
53
-
54
- # Redis Configuration
55
- REDIS_HOST=localhost
56
- REDIS_PORT=6379
57
-
58
- # Audit Logging
59
- POSTHOG_API_KEY=your_posthog_key
60
- POSTHOG_HOST=https://app.posthog.com
61
-
62
- # SQS Configuration (will be set during initialization)
63
- SQS_QUEUE_URL=https://sqs.region.amazonaws.com/account/queue-name
64
- ```
65
-
66
- The library will throw clear errors if any required environment variables are missing.
67
-
68
- ---
69
-
70
- ## Initialization (Required)
71
-
72
- Before using any service, you **must** call `initialize()` in your main entry file with the required configuration:
73
-
74
- ```ts
75
- import { initialize } from "dt-common-device";
76
-
77
- // Create a logger instance
78
- const logger = {
79
- info: (message: string, ...args: any[]) => console.log(message, ...args),
80
- warn: (message: string, ...args: any[]) => console.warn(message, ...args),
81
- error: (message: string, ...args: any[]) => console.error(message, ...args),
82
- };
83
-
84
- // Initialize the library
85
- await initialize({
86
- SOURCE: "ADMIN_SERVICE", // or "ACCESS_SERVICE" or "ENERGY_SERVICE"
87
- SQS_QUEUE_URL: "https://sqs.region.amazonaws.com/account/queue-name",
88
- DEVICE_SERVICE: "https://api.example.com/device", // Optional
89
- ADMIN_SERVICE: "https://api.example.com/admin", // Optional
90
- ACCESS_SERVICE: "https://api.example.com/access", // Optional
91
- ENERGY_SERVICE: "https://api.example.com/energy", // Optional
92
- INTERNAL_EVENT_HANDLER: {
93
- // Your event handler implementation
94
- handleEvent: async (event) => {
95
- // Handle internal events
96
- console.log("Handling event:", event);
97
- },
98
- },
99
- LOGGER: logger,
100
- });
101
- ```
102
-
103
- ### Configuration Options
104
-
105
- - `SOURCE`: Required. Must be one of: `"ADMIN_SERVICE"`, `"ACCESS_SERVICE"`, or `"ENERGY_SERVICE"`
106
- - `SQS_QUEUE_URL`: Required. Your AWS SQS queue URL
107
- - `DEVICE_SERVICE`, `ADMIN_SERVICE`, `ACCESS_SERVICE`, `ENERGY_SERVICE`: Optional service URLs
108
- - `INTERNAL_EVENT_HANDLER`: Required. Your event handler implementation
109
- - `LOGGER`: Required. Logger instance with info, warn, and error methods
110
-
111
- ---
112
-
113
- ## Available Services
114
-
115
- ### Local Device Service
116
-
117
- ```ts
118
- import { LocalDeviceService } from "dt-common-device";
119
-
120
- const deviceService = new LocalDeviceService();
121
-
122
- // Create a device
123
- const device = await deviceService.createDevice(deviceBody);
124
-
125
- // Get a device
126
- const device = await deviceService.getDevice(deviceId);
127
-
128
- // Get multiple devices
129
- const devices = await deviceService.getDevices(deviceIds);
130
-
131
- // Get devices by property
132
- const propertyDevices = await deviceService.getPropertyDevices(propertyId);
133
-
134
- // Update a device
135
- await deviceService.updateDevice(deviceId, updateBody);
136
-
137
- // Delete a device
138
- await deviceService.deleteDevice(deviceId);
139
-
140
- // State management
141
- const state = await deviceService.getState(deviceId);
142
- await deviceService.setState(deviceId, newState);
143
-
144
- // Status management
145
- const status = await deviceService.getStatus(deviceId);
146
- await deviceService.setStatus(
147
- deviceId,
148
- newStatus,
149
- "heartbeat",
150
- "Device went offline due to network connectivity issues"
151
- );
152
-
153
- // Battery management
154
- const battery = await deviceService.getBatteryLevel(deviceId);
155
- await deviceService.setBatteryLevel(deviceId, batteryLevel, "heartbeat");
156
-
157
- // Metadata management
158
- const metadata = await deviceService.getMetaData(deviceId);
159
- await deviceService.setMetaData(deviceId, metadata);
160
-
161
- // Query operations
162
- const devices = await deviceService.queryDevices(query);
163
- const count = await deviceService.queryCount(query);
164
- await deviceService.deleteDevices(query);
165
- ```
166
-
167
- ### Local Hub Service
168
-
169
- ```ts
170
- import { LocalHubService } from "dt-common-device";
171
-
172
- const hubService = new LocalHubService();
173
-
174
- // Add a hub
175
- const hub = await hubService.addHub(hubBody);
176
-
177
- // Get hubs
178
- const hubs = await hubService.getHubs(hubIds);
179
-
180
- // Get a single hub
181
- const hub = await hubService.getHub(hubId);
182
-
183
- // Update a hub
184
- await hubService.updateHub(hubId, updateBody);
185
-
186
- // Get hub status
187
- const status = await hubService.getStatus(hubId);
188
-
189
- // Delete a hub
190
- await hubService.deleteHub(hubId);
191
-
192
- // Delete multiple hubs
193
- await hubService.deleteAllHubs(hubIds);
194
- ```
195
-
196
- ### Local Schedule Service
197
-
198
- ```ts
199
- import { LocalScheduleService } from "dt-common-device";
200
-
201
- const scheduleService = new LocalScheduleService();
202
-
203
- // Get a schedule
204
- const schedule = await scheduleService.getSchedule(scheduleId);
205
-
206
- // Set a schedule
207
- await scheduleService.setSchedule(scheduleId, schedule);
208
-
209
- // Get schedule by zone
210
- const zoneSchedule = await scheduleService.getScheduleByZone(zoneId);
211
- ```
212
-
213
- ### Local Connection Service
214
-
215
- ```ts
216
- import { LocalConnectionService } from "dt-common-device";
217
-
218
- const connectionService = new LocalConnectionService();
219
-
220
- // Create a connection
221
- const connection = await connectionService.createConnection({
222
- connectionName: "My Connection",
223
- connectionRefId: "ref-123",
224
- propertyId: "prop-456",
225
- connectionProvider: "Sensibo",
226
- });
227
-
228
- // Get a connection
229
- const connection = await connectionService.getConnection(connectionId);
230
-
231
- // Update a connection
232
- await connectionService.updateConnection(connectionId, updateData);
233
- ```
234
-
235
- ### Cloud Device Service
236
-
237
- ```ts
238
- import { CloudDeviceService, DeviceFactory } from "dt-common-device";
239
-
240
- const cloudService = new CloudDeviceService();
241
- const deviceFactory = new DeviceFactory();
242
-
243
- // Get cloud devices (must implement in your project)
244
- // await cloudService.getDevices(connection);
245
-
246
- // Get device using factory
247
- const device = await deviceFactory.getDevice(deviceId);
248
- ```
249
-
250
- ### Property Service
251
-
252
- ```ts
253
- import { PropertyService } from "dt-common-device";
254
-
255
- const propertyService = new PropertyService();
256
-
257
- // Property operations (implementation specific)
258
- // await propertyService.getProperty(propertyId);
259
- ```
260
-
261
- ---
262
-
263
- ## Event System
264
-
265
- The library includes a comprehensive event handling system:
266
-
267
- ```ts
268
- import {
269
- EventHandler,
270
- EventProcessingService,
271
- DeviceEventHandler,
272
- EventHandlerOrchestrator,
273
- } from "dt-common-device";
274
-
275
- // Event handler for device operations
276
- const eventHandler = new EventHandler();
277
-
278
- // Device-specific event handler
279
- const deviceEventHandler = new DeviceEventHandler();
280
-
281
- // Event processing service
282
- const eventProcessingService = new EventProcessingService();
283
-
284
- // Event handler orchestrator
285
- const orchestrator = new EventHandlerOrchestrator();
286
- ```
287
-
288
- ---
289
-
290
- ## Queue System
291
-
292
- The library provides a hybrid HTTP queue system for managing HTTP requests:
293
-
294
- ```ts
295
- import { QueueService } from "dt-common-device";
296
-
297
- const queueService = new QueueService();
298
-
299
- // Make a rate-limited HTTP request
300
- const response = await queueService.request({
301
- method: "GET",
302
- url: "https://api.example.com/data",
303
- headers: { "Content-Type": "application/json" },
304
- queueOptions: {
305
- connectionId: "connection-123",
306
- connectionProvider: "Sensibo",
307
- microservice: "smart-energy",
308
- },
309
- });
310
- ```
311
-
312
- ### Features
313
-
314
- - **Rate Limiting**: Automatic rate limiting per provider and connection
315
- - **Retry Logic**: Exponential backoff with configurable retry attempts
316
- - **Audit Logging**: Automatic audit logging for all requests
317
- - **Queue Management**: Redis-based queue with BullMQ
318
- - **Error Handling**: Comprehensive error handling and logging
319
-
320
- ---
321
-
322
- ## Alert and Issue Management
323
-
324
- ```ts
325
- import {
326
- AlertService,
327
- IssueService,
328
- AlertBuilder,
329
- IssueBuilder,
330
- } from "dt-common-device";
331
-
332
- // Alert service
333
- const alertService = new AlertService();
334
-
335
- // Issue service
336
- const issueService = new IssueService();
337
-
338
- // Build alerts
339
- const alert = new AlertBuilder()
340
- .setTitle("Device Offline")
341
- .setDescription("Device has been offline for more than 24 hours")
342
- .setSeverity("HIGH")
343
- .build();
344
-
345
- // Build issues
346
- const issue = new IssueBuilder()
347
- .setTitle("Connection Failed")
348
- .setDescription("Failed to connect to device")
349
- .setPriority("HIGH")
350
- .build();
351
- ```
352
-
353
- ---
354
-
355
- ## Graceful Shutdown
356
-
357
- ```ts
358
- import { shutdown } from "dt-common-device";
359
-
360
- // Gracefully shutdown the library
361
- await shutdown();
362
- ```
363
-
364
- ---
365
-
366
- ## Importing Types and Interfaces
367
-
368
- All types and interfaces are available as named exports:
369
-
370
- ```ts
371
- import {
372
- IDevice,
373
- IHub,
374
- IConnection,
375
- ISchedule,
376
- IProperty,
377
- IAlert,
378
- IIssue,
379
- } from "dt-common-device";
380
- ```
381
-
382
- ---
383
-
384
- ## Dependencies
385
-
386
- The library requires the following major dependencies:
387
-
388
- - `axios`: HTTP client
389
- - `bullmq`: Queue management
390
- - `dt-audit-library`: Audit logging
391
- - `dt-pub-sub`: Event publishing/subscribing
392
- - `ioredis`: Redis client
393
- - `mongoose`: MongoDB ODM
394
- - `pg`: PostgreSQL client
395
- - `typedi`: Dependency injection
396
-
397
- ---
398
-
399
- ## Notes
400
-
401
- - You **must** call `initialize()` before using any service. If not, you will get a runtime error.
402
- - **You must set `POSTHOG_API_KEY` and `POSTHOG_HOST` in your environment before using any local device service.**
403
- - You do **not** need to call `initializeAudit()`
404
-
405
- ## Rate Limiting
406
-
407
- The queue system now implements intelligent rate limiting that delays requests instead of dropping them when rate limits are exceeded.
408
-
409
- ### How it works
410
-
411
- 1. **Rate Limit Check**: Before processing each HTTP request, the system checks if the rate limit for the provider/connection combination has been exceeded.
412
-
413
- 2. **Delay Instead of Drop**: If the rate limit is exceeded, instead of immediately failing the request, the system:
414
-
415
- - Calculates when the next request can be processed (after the rate limit window expires)
416
- - Delays the job execution by the calculated time
417
- - Re-checks the rate limit after the delay
418
- - Only fails if still rate limited after the delay
419
-
420
- 3. **Example Scenario**:
421
-
422
- ```
423
- Rate Limit: 5 requests per 60 seconds
424
-
425
- Timeline:
426
- 0s: Request 1 (processed immediately)
427
- 10s: Request 2 (processed immediately)
428
- 20s: Request 3 (processed immediately)
429
- 30s: Request 4 (processed immediately)
430
- 40s: Request 5 (processed immediately)
431
- 45s: Request 6 (delayed by 15s, processed at 60s)
432
- 50s: Request 7 (delayed by 10s, processed at 60s)
433
- ```
434
-
435
- ### Configuration
436
-
437
- Rate limits are configured per provider in `src/queue/utils/rateLimit.utils.ts`:
438
-
439
- ```typescript
440
- configs.set("Sensibo", {
441
- maxRequests: 5, // Maximum requests allowed
442
- windowMs: 60000, // Time window in milliseconds
443
- provider: "Sensibo",
444
- });
445
- ```
446
-
447
- ### Benefits
448
-
449
- - **No Lost Requests**: Requests are delayed rather than dropped
450
- - **Automatic Recovery**: System automatically processes delayed requests when rate limits reset
451
- - **Better User Experience**: Users don't see immediate failures due to rate limits
452
- - **Audit Trail**: All rate limit events are logged for monitoring
1
+ # dt-common-device
2
+
3
+ A comprehensive TypeScript library for device management, supporting both cloud and local device operations with advanced event handling, queue management, and audit logging.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install dt-common-device
9
+ ```
10
+
11
+ ## Environment Variables Required
12
+
13
+ **The following environment variables are REQUIRED for the library to function:**
14
+
15
+ ### AWS Configuration
16
+
17
+ - `AWS_SECRET_ACCESS_KEY` — Your AWS secret access key
18
+ - `AWS_REGION` — Your AWS region
19
+ - `AWS_ACCESS_KEY_ID` — Your AWS access key ID
20
+ - `EVENT_BUS_NAME` — Your AWS EventBridge event bus name
21
+
22
+ ### Database Configuration
23
+
24
+ - `ADMIN_DB_URI` — PostgreSQL database connection URI
25
+ - `MONGODB_URI` — MongoDB database connection URI
26
+
27
+ ### Redis Configuration
28
+
29
+ - `REDIS_HOST` — Redis server host
30
+ - `REDIS_PORT` — Redis server port
31
+
32
+ ### Audit Logging
33
+
34
+ - `POSTHOG_API_KEY` — Your PostHog API key
35
+ - `POSTHOG_HOST` — The PostHog host URL
36
+
37
+ ### SQS Configuration
38
+
39
+ - `SQS_QUEUE_URL` — AWS SQS queue URL (configured during initialization)
40
+
41
+ Example `.env` file:
42
+
43
+ ```
44
+ # AWS Configuration
45
+ AWS_SECRET_ACCESS_KEY=your_secret_key
46
+ AWS_REGION=us-east-1
47
+ AWS_ACCESS_KEY_ID=your_access_key
48
+ EVENT_BUS_NAME=your-event-bus
49
+
50
+ # Database Configuration
51
+ ADMIN_DB_URI=postgres://username:password@host:port/database
52
+ MONGODB_URI=mongodb://username:password@host:port/database
53
+
54
+ # Redis Configuration
55
+ REDIS_HOST=localhost
56
+ REDIS_PORT=6379
57
+
58
+ # Audit Logging
59
+ POSTHOG_API_KEY=your_posthog_key
60
+ POSTHOG_HOST=https://app.posthog.com
61
+
62
+ # SQS Configuration (will be set during initialization)
63
+ SQS_QUEUE_URL=https://sqs.region.amazonaws.com/account/queue-name
64
+ ```
65
+
66
+ The library will throw clear errors if any required environment variables are missing.
67
+
68
+ ---
69
+
70
+ ## Initialization (Required)
71
+
72
+ Before using any service, you **must** call `initialize()` in your main entry file with the required configuration:
73
+
74
+ ```ts
75
+ import { initialize } from "dt-common-device";
76
+
77
+ // Create a logger instance
78
+ const logger = {
79
+ info: (message: string, ...args: any[]) => console.log(message, ...args),
80
+ warn: (message: string, ...args: any[]) => console.warn(message, ...args),
81
+ error: (message: string, ...args: any[]) => console.error(message, ...args),
82
+ };
83
+
84
+ // Initialize the library
85
+ await initialize({
86
+ SOURCE: "ADMIN_SERVICE", // or "ACCESS_SERVICE" or "ENERGY_SERVICE"
87
+ SQS_QUEUE_URL: "https://sqs.region.amazonaws.com/account/queue-name",
88
+ DEVICE_SERVICE: "https://api.example.com/device", // Optional
89
+ ADMIN_SERVICE: "https://api.example.com/admin", // Optional
90
+ ACCESS_SERVICE: "https://api.example.com/access", // Optional
91
+ ENERGY_SERVICE: "https://api.example.com/energy", // Optional
92
+ INTERNAL_EVENT_HANDLER: {
93
+ // Your event handler implementation
94
+ handleEvent: async (event) => {
95
+ // Handle internal events
96
+ console.log("Handling event:", event);
97
+ },
98
+ },
99
+ LOGGER: logger,
100
+ });
101
+ ```
102
+
103
+ ### Configuration Options
104
+
105
+ - `SOURCE`: Required. Must be one of: `"ADMIN_SERVICE"`, `"ACCESS_SERVICE"`, or `"ENERGY_SERVICE"`
106
+ - `SQS_QUEUE_URL`: Required. Your AWS SQS queue URL
107
+ - `DEVICE_SERVICE`, `ADMIN_SERVICE`, `ACCESS_SERVICE`, `ENERGY_SERVICE`: Optional service URLs
108
+ - `INTERNAL_EVENT_HANDLER`: Required. Your event handler implementation
109
+ - `LOGGER`: Required. Logger instance with info, warn, and error methods
110
+
111
+ ---
112
+
113
+ ## Available Services
114
+
115
+ ### Local Device Service
116
+
117
+ ```ts
118
+ import { LocalDeviceService } from "dt-common-device";
119
+
120
+ const deviceService = new LocalDeviceService();
121
+
122
+ // Create a device
123
+ const device = await deviceService.createDevice(deviceBody);
124
+
125
+ // Get a device
126
+ const device = await deviceService.getDevice(deviceId);
127
+
128
+ // Get multiple devices
129
+ const devices = await deviceService.getDevices(deviceIds);
130
+
131
+ // Get devices by property
132
+ const propertyDevices = await deviceService.getPropertyDevices(propertyId);
133
+
134
+ // Update a device
135
+ await deviceService.updateDevice(deviceId, updateBody);
136
+
137
+ // Delete a device
138
+ await deviceService.deleteDevice(deviceId);
139
+
140
+ // State management
141
+ const state = await deviceService.getState(deviceId);
142
+ await deviceService.setState(deviceId, newState);
143
+
144
+ // Status management
145
+ const status = await deviceService.getStatus(deviceId);
146
+ await deviceService.setStatus(
147
+ deviceId,
148
+ newStatus,
149
+ "heartbeat",
150
+ "Device went offline due to network connectivity issues"
151
+ );
152
+
153
+ // Battery management
154
+ const battery = await deviceService.getBatteryLevel(deviceId);
155
+ await deviceService.setBatteryLevel(deviceId, batteryLevel, "heartbeat");
156
+
157
+ // Metadata management
158
+ const metadata = await deviceService.getMetaData(deviceId);
159
+ await deviceService.setMetaData(deviceId, metadata);
160
+
161
+ // Query operations
162
+ const devices = await deviceService.queryDevices(query);
163
+ const count = await deviceService.queryCount(query);
164
+ await deviceService.deleteDevices(query);
165
+ ```
166
+
167
+ ### Local Hub Service
168
+
169
+ ```ts
170
+ import { LocalHubService } from "dt-common-device";
171
+
172
+ const hubService = new LocalHubService();
173
+
174
+ // Add a hub
175
+ const hub = await hubService.addHub(hubBody);
176
+
177
+ // Get hubs
178
+ const hubs = await hubService.getHubs(hubIds);
179
+
180
+ // Get a single hub
181
+ const hub = await hubService.getHub(hubId);
182
+
183
+ // Update a hub
184
+ await hubService.updateHub(hubId, updateBody);
185
+
186
+ // Get hub status
187
+ const status = await hubService.getStatus(hubId);
188
+
189
+ // Delete a hub
190
+ await hubService.deleteHub(hubId);
191
+
192
+ // Delete multiple hubs
193
+ await hubService.deleteAllHubs(hubIds);
194
+ ```
195
+
196
+ ### Local Schedule Service
197
+
198
+ ```ts
199
+ import { LocalScheduleService } from "dt-common-device";
200
+
201
+ const scheduleService = new LocalScheduleService();
202
+
203
+ // Get a schedule
204
+ const schedule = await scheduleService.getSchedule(scheduleId);
205
+
206
+ // Set a schedule
207
+ await scheduleService.setSchedule(scheduleId, schedule);
208
+
209
+ // Get schedule by zone
210
+ const zoneSchedule = await scheduleService.getScheduleByZone(zoneId);
211
+ ```
212
+
213
+ ### Local Connection Service
214
+
215
+ ```ts
216
+ import { LocalConnectionService } from "dt-common-device";
217
+
218
+ const connectionService = new LocalConnectionService();
219
+
220
+ // Create a connection
221
+ const connection = await connectionService.createConnection({
222
+ connectionName: "My Connection",
223
+ connectionRefId: "ref-123",
224
+ propertyId: "prop-456",
225
+ connectionProvider: "Sensibo",
226
+ });
227
+
228
+ // Get a connection
229
+ const connection = await connectionService.getConnection(connectionId);
230
+
231
+ // Update a connection
232
+ await connectionService.updateConnection(connectionId, updateData);
233
+ ```
234
+
235
+ ### Cloud Device Service
236
+
237
+ ```ts
238
+ import { CloudDeviceService, DeviceFactory } from "dt-common-device";
239
+
240
+ const cloudService = new CloudDeviceService();
241
+ const deviceFactory = new DeviceFactory();
242
+
243
+ // Get cloud devices (must implement in your project)
244
+ // await cloudService.getDevices(connection);
245
+
246
+ // Get device using factory
247
+ const device = await deviceFactory.getDevice(deviceId);
248
+ ```
249
+
250
+ ### Property Service
251
+
252
+ ```ts
253
+ import { PropertyService } from "dt-common-device";
254
+
255
+ const propertyService = new PropertyService();
256
+
257
+ // Property operations (implementation specific)
258
+ // await propertyService.getProperty(propertyId);
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Event System
264
+
265
+ The library includes a comprehensive event handling system:
266
+
267
+ ```ts
268
+ import {
269
+ EventHandler,
270
+ EventProcessingService,
271
+ DeviceEventHandler,
272
+ EventHandlerOrchestrator,
273
+ } from "dt-common-device";
274
+
275
+ // Event handler for device operations
276
+ const eventHandler = new EventHandler();
277
+
278
+ // Device-specific event handler
279
+ const deviceEventHandler = new DeviceEventHandler();
280
+
281
+ // Event processing service
282
+ const eventProcessingService = new EventProcessingService();
283
+
284
+ // Event handler orchestrator
285
+ const orchestrator = new EventHandlerOrchestrator();
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Queue System
291
+
292
+ The library provides a hybrid HTTP queue system for managing HTTP requests:
293
+
294
+ ```ts
295
+ import { QueueService } from "dt-common-device";
296
+
297
+ const queueService = new QueueService();
298
+
299
+ // Make a rate-limited HTTP request
300
+ const response = await queueService.request({
301
+ method: "GET",
302
+ url: "https://api.example.com/data",
303
+ headers: { "Content-Type": "application/json" },
304
+ queueOptions: {
305
+ connectionId: "connection-123",
306
+ connectionProvider: "Sensibo",
307
+ microservice: "smart-energy",
308
+ },
309
+ });
310
+ ```
311
+
312
+ ### Features
313
+
314
+ - **Rate Limiting**: Automatic rate limiting per provider and connection
315
+ - **Retry Logic**: Exponential backoff with configurable retry attempts
316
+ - **Audit Logging**: Automatic audit logging for all requests
317
+ - **Queue Management**: Redis-based queue with BullMQ
318
+ - **Error Handling**: Comprehensive error handling and logging
319
+
320
+ ---
321
+
322
+ ## Alert and Issue Management
323
+
324
+ ```ts
325
+ import {
326
+ AlertService,
327
+ IssueService,
328
+ AlertBuilder,
329
+ IssueBuilder,
330
+ } from "dt-common-device";
331
+
332
+ // Alert service
333
+ const alertService = new AlertService();
334
+
335
+ // Issue service
336
+ const issueService = new IssueService();
337
+
338
+ // Build alerts
339
+ const alert = new AlertBuilder()
340
+ .setTitle("Device Offline")
341
+ .setDescription("Device has been offline for more than 24 hours")
342
+ .setSeverity("HIGH")
343
+ .build();
344
+
345
+ // Build issues
346
+ const issue = new IssueBuilder()
347
+ .setTitle("Connection Failed")
348
+ .setDescription("Failed to connect to device")
349
+ .setPriority("HIGH")
350
+ .build();
351
+ ```
352
+
353
+ ---
354
+
355
+ ## Graceful Shutdown
356
+
357
+ ```ts
358
+ import { shutdown } from "dt-common-device";
359
+
360
+ // Gracefully shutdown the library
361
+ await shutdown();
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Importing Types and Interfaces
367
+
368
+ All types and interfaces are available as named exports:
369
+
370
+ ```ts
371
+ import {
372
+ IDevice,
373
+ IHub,
374
+ IConnection,
375
+ ISchedule,
376
+ IProperty,
377
+ IAlert,
378
+ IIssue,
379
+ } from "dt-common-device";
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Dependencies
385
+
386
+ The library requires the following major dependencies:
387
+
388
+ - `axios`: HTTP client
389
+ - `bullmq`: Queue management
390
+ - `dt-audit-library`: Audit logging
391
+ - `dt-pub-sub`: Event publishing/subscribing
392
+ - `ioredis`: Redis client
393
+ - `mongoose`: MongoDB ODM
394
+ - `pg`: PostgreSQL client
395
+ - `typedi`: Dependency injection
396
+
397
+ ---
398
+
399
+ ## Notes
400
+
401
+ - You **must** call `initialize()` before using any service. If not, you will get a runtime error.
402
+ - **You must set `POSTHOG_API_KEY` and `POSTHOG_HOST` in your environment before using any local device service.**
403
+ - You do **not** need to call `initializeAudit()`
404
+
405
+ ## Rate Limiting
406
+
407
+ The queue system now implements intelligent rate limiting that delays requests instead of dropping them when rate limits are exceeded.
408
+
409
+ ### How it works
410
+
411
+ 1. **Rate Limit Check**: Before processing each HTTP request, the system checks if the rate limit for the provider/connection combination has been exceeded.
412
+
413
+ 2. **Delay Instead of Drop**: If the rate limit is exceeded, instead of immediately failing the request, the system:
414
+
415
+ - Calculates when the next request can be processed (after the rate limit window expires)
416
+ - Delays the job execution by the calculated time
417
+ - Re-checks the rate limit after the delay
418
+ - Only fails if still rate limited after the delay
419
+
420
+ 3. **Example Scenario**:
421
+
422
+ ```
423
+ Rate Limit: 5 requests per 60 seconds
424
+
425
+ Timeline:
426
+ 0s: Request 1 (processed immediately)
427
+ 10s: Request 2 (processed immediately)
428
+ 20s: Request 3 (processed immediately)
429
+ 30s: Request 4 (processed immediately)
430
+ 40s: Request 5 (processed immediately)
431
+ 45s: Request 6 (delayed by 15s, processed at 60s)
432
+ 50s: Request 7 (delayed by 10s, processed at 60s)
433
+ ```
434
+
435
+ ### Configuration
436
+
437
+ Rate limits are configured per provider in `src/queue/utils/rateLimit.utils.ts`:
438
+
439
+ ```typescript
440
+ configs.set("Sensibo", {
441
+ maxRequests: 5, // Maximum requests allowed
442
+ windowMs: 60000, // Time window in milliseconds
443
+ provider: "Sensibo",
444
+ });
445
+ ```
446
+
447
+ ### Benefits
448
+
449
+ - **No Lost Requests**: Requests are delayed rather than dropped
450
+ - **Automatic Recovery**: System automatically processes delayed requests when rate limits reset
451
+ - **Better User Experience**: Users don't see immediate failures due to rate limits
452
+ - **Audit Trail**: All rate limit events are logged for monitoring
@@ -24,4 +24,5 @@ export declare const CONNECTION_PROVIDERS: {
24
24
  readonly TWILIO: "Twilio";
25
25
  readonly DAIKIN: "Daikin";
26
26
  readonly HONEYWELL: "HoneyWell";
27
+ readonly ULTRALOCK: "UltraLock";
27
28
  };
@@ -30,4 +30,5 @@ exports.CONNECTION_PROVIDERS = {
30
30
  TWILIO: "Twilio",
31
31
  DAIKIN: "Daikin",
32
32
  HONEYWELL: "HoneyWell",
33
+ ULTRALOCK: "UltraLock",
33
34
  };
@@ -99,19 +99,19 @@ let AdminRepository = (() => {
99
99
  return [];
100
100
  }
101
101
  // If not cached, get the result from the database
102
- const result = await this.postgres.query(`SELECT
103
- "zc"."id" AS "zoneCollectionMapId",
104
- "zc"."collectionId",
105
- "zc"."zoneId",
106
- "z"."id" AS "zoneId",
107
- "z"."name" AS "zoneName",
108
- "z"."zoneTypeId",
109
- "zt"."name" AS "zoneTypeName",
110
- "z"."isUnderMaintenance"
111
- FROM "dt_zones_collection_map" AS "zc"
112
- INNER JOIN "dt_zones" AS "z" ON "zc"."zoneId" = "z"."id"
113
- LEFT JOIN "dt_zoneTypes" AS "zt" ON "z"."zoneTypeId" = "zt"."id"
114
- WHERE "zc"."collectionId" = ANY($1)
102
+ const result = await this.postgres.query(`SELECT
103
+ "zc"."id" AS "zoneCollectionMapId",
104
+ "zc"."collectionId",
105
+ "zc"."zoneId",
106
+ "z"."id" AS "zoneId",
107
+ "z"."name" AS "zoneName",
108
+ "z"."zoneTypeId",
109
+ "zt"."name" AS "zoneTypeName",
110
+ "z"."isUnderMaintenance"
111
+ FROM "dt_zones_collection_map" AS "zc"
112
+ INNER JOIN "dt_zones" AS "z" ON "zc"."zoneId" = "z"."id"
113
+ LEFT JOIN "dt_zoneTypes" AS "zt" ON "z"."zoneTypeId" = "zt"."id"
114
+ WHERE "zc"."collectionId" = ANY($1)
115
115
  ORDER BY "zc"."id" ASC`, [accessGroupIds]);
116
116
  const response = result.rows;
117
117
  const _zones = (nestedZones, allZones = []) => {
@@ -280,10 +280,10 @@ let AdminRepository = (() => {
280
280
  }
281
281
  async getZonesByAccessGroups(accessGroupIds, type) {
282
282
  // Fetch zone IDs associated with these access groups
283
- const zonesIdsQuery = `
284
- SELECT DISTINCT z."zoneId"
285
- FROM dt_zones_collection_map z
286
- WHERE z."collectionId" = ANY($1)
283
+ const zonesIdsQuery = `
284
+ SELECT DISTINCT z."zoneId"
285
+ FROM dt_zones_collection_map z
286
+ WHERE z."collectionId" = ANY($1)
287
287
  `;
288
288
  const zonesIdsResult = await this.postgres.query(zonesIdsQuery, [
289
289
  accessGroupIds,
@@ -296,20 +296,20 @@ let AdminRepository = (() => {
296
296
  const zoneTypesToFilter = type || ["Guest Room", "Room"];
297
297
  // Fetch zone type IDs for the specified type
298
298
  // Using IN with unnest() for better compatibility with string arrays
299
- const zoneTypesQuery = `
300
- SELECT id
301
- FROM "dt_zoneTypes"
302
- WHERE name IN (SELECT unnest($1::text[]))
299
+ const zoneTypesQuery = `
300
+ SELECT id
301
+ FROM "dt_zoneTypes"
302
+ WHERE name IN (SELECT unnest($1::text[]))
303
303
  `;
304
304
  const zoneTypes = await this.postgres.query(zoneTypesQuery, [
305
305
  zoneTypesToFilter,
306
306
  ]);
307
307
  const guestRoomZoneTypeIds = zoneTypes.rows.map((e) => e.id);
308
308
  // Fetch zones matching both sets of IDs
309
- const zonesQuery = `
310
- SELECT id
311
- FROM dt_zones
312
- WHERE id = ANY($1) AND "zoneTypeId" = ANY($2)
309
+ const zonesQuery = `
310
+ SELECT id
311
+ FROM dt_zones
312
+ WHERE id = ANY($1) AND "zoneTypeId" = ANY($2)
313
313
  `;
314
314
  const zonesResult = await this.postgres.query(zonesQuery, [
315
315
  zonesIds,
@@ -320,15 +320,15 @@ let AdminRepository = (() => {
320
320
  async getAccessGroup(accessGroupId, propertyId) {
321
321
  let query;
322
322
  if (propertyId) {
323
- query = `
324
- SELECT * FROM dt_collections
325
- WHERE "id" = $1 AND "propertyId" = $2
323
+ query = `
324
+ SELECT * FROM dt_collections
325
+ WHERE "id" = $1 AND "propertyId" = $2
326
326
  `;
327
327
  }
328
328
  else {
329
- query = `
330
- SELECT * FROM dt_collections
331
- WHERE "id" = $1
329
+ query = `
330
+ SELECT * FROM dt_collections
331
+ WHERE "id" = $1
332
332
  `;
333
333
  }
334
334
  const params = propertyId ? [accessGroupId, propertyId] : [accessGroupId];
@@ -339,9 +339,9 @@ let AdminRepository = (() => {
339
339
  return null;
340
340
  }
341
341
  async getZoneAccessGroupByZoneId(zoneId) {
342
- const query = `
343
- SELECT * FROM dt_zones_collection_map
344
- WHERE "zoneId" = $1
342
+ const query = `
343
+ SELECT * FROM dt_zones_collection_map
344
+ WHERE "zoneId" = $1
345
345
  `;
346
346
  const result = await this.postgres.query(query, [zoneId]);
347
347
  if (result.rows.length > 0) {
@@ -353,10 +353,10 @@ let AdminRepository = (() => {
353
353
  if (accessGroupIds.length === 0) {
354
354
  return [];
355
355
  }
356
- const query = `
357
- SELECT DISTINCT "zoneId"
358
- FROM dt_zones_collection_map
359
- WHERE "collectionId" = ANY($1)
356
+ const query = `
357
+ SELECT DISTINCT "zoneId"
358
+ FROM dt_zones_collection_map
359
+ WHERE "collectionId" = ANY($1)
360
360
  `;
361
361
  const result = await this.postgres.query(query, [accessGroupIds]);
362
362
  return result.rows.map((row) => row.zoneId);
@@ -446,12 +446,12 @@ let AdminRepository = (() => {
446
446
  return [];
447
447
  }
448
448
  async getAccessGroupsByZoneId(zoneId) {
449
- const query = `
450
- SELECT DISTINCT c.*
451
- FROM dt_collections c
452
- INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
453
- WHERE zcm."zoneId" = $1
454
- AND c."isDeleted" = false
449
+ const query = `
450
+ SELECT DISTINCT c.*
451
+ FROM dt_collections c
452
+ INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
453
+ WHERE zcm."zoneId" = $1
454
+ AND c."isDeleted" = false
455
455
  `;
456
456
  const result = await this.postgres.query(query, [zoneId]);
457
457
  return result.rows;
@@ -461,12 +461,12 @@ let AdminRepository = (() => {
461
461
  return [];
462
462
  }
463
463
  // Get all access groups associated with any of these zones
464
- const query = `
465
- SELECT DISTINCT c.*
466
- FROM dt_collections c
467
- INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
468
- WHERE zcm."zoneId" = ANY($1)
469
- AND c."isDeleted" = false
464
+ const query = `
465
+ SELECT DISTINCT c.*
466
+ FROM dt_collections c
467
+ INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
468
+ WHERE zcm."zoneId" = ANY($1)
469
+ AND c."isDeleted" = false
470
470
  `;
471
471
  const result = await this.postgres.query(query, [zoneIds]);
472
472
  return result.rows;
@@ -124,9 +124,9 @@ let ConnectionRepository = (() => {
124
124
  .map((key, index) => `"${key}" = $${index + 3}`)
125
125
  .join(", ");
126
126
  const values = Object.values(data);
127
- const result = await this.pool.query(`UPDATE dt_connections
128
- SET ${setClause}, "updatedAt" = NOW()
129
- WHERE id = $1 AND "propertyId" = $2
127
+ const result = await this.pool.query(`UPDATE dt_connections
128
+ SET ${setClause}, "updatedAt" = NOW()
129
+ WHERE id = $1 AND "propertyId" = $2
130
130
  RETURNING *`, [connectionId, propertyId, ...values]);
131
131
  if (result.rowCount === 0) {
132
132
  return null;
@@ -31,5 +31,6 @@ export declare enum ConnectionProvider {
31
31
  Sifely = "Sifely",
32
32
  Twilio = "Twilio",
33
33
  Daikin = "Daikin",
34
- HoneyWell = "HoneyWell"
34
+ HoneyWell = "HoneyWell",
35
+ UltraLock = "UltraLock"
35
36
  }
@@ -22,4 +22,5 @@ var ConnectionProvider;
22
22
  ConnectionProvider["Twilio"] = "Twilio";
23
23
  ConnectionProvider["Daikin"] = "Daikin";
24
24
  ConnectionProvider["HoneyWell"] = "HoneyWell";
25
+ ConnectionProvider["UltraLock"] = "UltraLock";
25
26
  })(ConnectionProvider || (exports.ConnectionProvider = ConnectionProvider = {}));
@@ -235,8 +235,8 @@ let DeviceRepository = (() => {
235
235
  }
236
236
  async getDevicesByAccessGroup(accessGroupId) {
237
237
  try {
238
- const result = await this.postgres.query(`SELECT d.* FROM dt_devices d
239
- INNER JOIN dt_zones_collection_map zcm ON d.zoneId = zcm.zoneId
238
+ const result = await this.postgres.query(`SELECT d.* FROM dt_devices d
239
+ INNER JOIN dt_zones_collection_map zcm ON d.zoneId = zcm.zoneId
240
240
  WHERE zcm.collectionId = $1`, [accessGroupId]);
241
241
  if (result.rows.length > 0) {
242
242
  return result.rows;
@@ -50,7 +50,8 @@ export declare enum EntitySubType {
50
50
  SCHLAGE = "SCHLAGE",
51
51
  LOCKLY = "LOCKLY",
52
52
  SIFELY = "SIFELY",
53
- GEOCODING = "GEOCODING"
53
+ GEOCODING = "GEOCODING",
54
+ ULTRALOCK = "ULTRALOCK"
54
55
  }
55
56
  export declare enum IssueStatus {
56
57
  PENDING = "PENDING",
@@ -62,6 +62,7 @@ var EntitySubType;
62
62
  EntitySubType["SIFELY"] = "SIFELY";
63
63
  // OTHER
64
64
  EntitySubType["GEOCODING"] = "GEOCODING";
65
+ EntitySubType["ULTRALOCK"] = "ULTRALOCK";
65
66
  })(EntitySubType || (exports.EntitySubType = EntitySubType = {}));
66
67
  var IssueStatus;
67
68
  (function (IssueStatus) {
@@ -147,6 +147,12 @@ class RateLimitUtils {
147
147
  provider: constants_1.CONNECTION_PROVIDERS.DUSAW,
148
148
  maxTimeoutWindowMs: 120000,
149
149
  });
150
+ configs.set(constants_1.CONNECTION_PROVIDERS.ULTRALOCK, {
151
+ maxRequests: 60,
152
+ windowMs: 60000, // 1 minute — adjust once UltraLock API docs confirm their limit
153
+ provider: constants_1.CONNECTION_PROVIDERS.ULTRALOCK,
154
+ maxTimeoutWindowMs: 120000,
155
+ });
150
156
  return configs;
151
157
  }
152
158
  static async isRateLimitAllowed(connectionId, provider, rateLimitConfigs) {
@@ -51,8 +51,8 @@ let WebHookRepository = (() => {
51
51
  this.postgres = (0, db_1.getWebhookPostgresClient)();
52
52
  }
53
53
  async getWebhookAppByUid(uid) {
54
- const query = `
55
- SELECT * FROM application WHERE uid = $1
54
+ const query = `
55
+ SELECT * FROM application WHERE uid = $1
56
56
  `;
57
57
  const result = await this.postgres.query(query, [uid]);
58
58
  if (result.rows.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dt-common-device",
3
- "version": "13.10.6",
3
+ "version": "13.10.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [