@mango-power/node-kit 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mqtt/ali-mqtt.service.d.ts +36 -0
- package/dist/mqtt/ali-mqtt.service.js +244 -0
- package/dist/mqtt/aws-mqtt.service.d.ts +37 -0
- package/dist/mqtt/aws-mqtt.service.js +238 -0
- package/dist/mqtt/emqx-mqtt.service.d.ts +1 -1
- package/dist/mqtt/mqtt.type.d.ts +12 -0
- package/dist/pg.service.d.ts +5 -4
- package/dist/pg.service.js +13 -7
- package/dist/redis.service.d.ts +6 -5
- package/dist/redis.service.js +21 -16
- package/dist/rmq.service.d.ts +4 -2
- package/dist/sls.service.d.ts +4 -1
- package/dist/test/smoke.test.js +4 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Shared node toolkit package for MP services.
|
|
|
9
9
|
- `RedisClient`
|
|
10
10
|
- `RmqService`
|
|
11
11
|
- `SLSClient`
|
|
12
|
+
- `AwsMqttService`
|
|
13
|
+
- `AliMqttService`
|
|
12
14
|
- `EmqxMqttService` / `EMQXMqttService`
|
|
13
15
|
|
|
14
16
|
## Build
|
|
@@ -34,6 +36,6 @@ registry=https://registry.npmjs.org/
|
|
|
34
36
|
## Install from other projects
|
|
35
37
|
|
|
36
38
|
```bash
|
|
37
|
-
npm i @mango-power/node-kit@^0.0.
|
|
39
|
+
npm i @mango-power/node-kit@^0.0.6
|
|
38
40
|
```
|
|
39
41
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -20,4 +20,6 @@ __exportStar(require("./redis.service"), exports);
|
|
|
20
20
|
__exportStar(require("./rmq.service"), exports);
|
|
21
21
|
__exportStar(require("./sls.service"), exports);
|
|
22
22
|
__exportStar(require("./mqtt/mqtt.type"), exports);
|
|
23
|
+
__exportStar(require("./mqtt/aws-mqtt.service"), exports);
|
|
24
|
+
__exportStar(require("./mqtt/ali-mqtt.service"), exports);
|
|
23
25
|
__exportStar(require("./mqtt/emqx-mqtt.service"), exports);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
|
+
import { AliyunMqttConfig, MqttTopicRunner } from "./mqtt.type";
|
|
3
|
+
type MqttQos = 0 | 1 | 2;
|
|
4
|
+
interface MqttMessageContext {
|
|
5
|
+
topic: string;
|
|
6
|
+
payload: Buffer;
|
|
7
|
+
match?: string;
|
|
8
|
+
}
|
|
9
|
+
interface MqttHandlerRegistration {
|
|
10
|
+
id?: string;
|
|
11
|
+
topicPattern: string;
|
|
12
|
+
qos: MqttQos;
|
|
13
|
+
handler: (ctx: MqttMessageContext) => Promise<void> | void;
|
|
14
|
+
}
|
|
15
|
+
export declare class AliMqttService {
|
|
16
|
+
client: mqtt.MqttClient;
|
|
17
|
+
private handlerMap;
|
|
18
|
+
private topicHandlerMap;
|
|
19
|
+
private hasConnectedOnce;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
init(config: AliyunMqttConfig): Promise<void>;
|
|
22
|
+
private onError;
|
|
23
|
+
private onMessage;
|
|
24
|
+
private onConnect;
|
|
25
|
+
private subscribe;
|
|
26
|
+
private isTopicMatch;
|
|
27
|
+
private isWildcardPattern;
|
|
28
|
+
private getMatchValue;
|
|
29
|
+
publish(topic: string, message: string | Buffer, opts: mqtt.IClientPublishOptions): Promise<void>;
|
|
30
|
+
registerService(service: MqttTopicRunner): Promise<string>;
|
|
31
|
+
unregisterService(id: string): Promise<void>;
|
|
32
|
+
subscribeHandler(registration: MqttHandlerRegistration): Promise<string>;
|
|
33
|
+
unsubscribeHandler(id: string): Promise<void>;
|
|
34
|
+
clearService(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.AliMqttService = void 0;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const crypto_1 = require("crypto");
|
|
42
|
+
const mqtt_1 = __importDefault(require("mqtt"));
|
|
43
|
+
const mqtt_pattern_1 = __importDefault(require("mqtt-pattern"));
|
|
44
|
+
const logger_1 = require("../logger");
|
|
45
|
+
function createAliyunMqttClientId(groupName, appName) {
|
|
46
|
+
return `${groupName}@@@${appName}_${(0, crypto_1.randomUUID)()}`;
|
|
47
|
+
}
|
|
48
|
+
function createAliyunMqttAuthInfo(clientId, instanceId, accessKeyId, accessKeySecret) {
|
|
49
|
+
const username = `Signature|${accessKeyId}|${instanceId}`;
|
|
50
|
+
const password = crypto.createHmac("sha1", accessKeySecret).update(clientId).digest("base64");
|
|
51
|
+
return { username, password };
|
|
52
|
+
}
|
|
53
|
+
class AliMqttService {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.handlerMap = new Map();
|
|
56
|
+
this.topicHandlerMap = new Map();
|
|
57
|
+
this.hasConnectedOnce = false;
|
|
58
|
+
this.logger = (0, logger_1.createLogger)(AliMqttService.name);
|
|
59
|
+
}
|
|
60
|
+
async init(config) {
|
|
61
|
+
const clientId = createAliyunMqttClientId("GID_MP_SVC", "iot_svc_task");
|
|
62
|
+
const { username, password } = createAliyunMqttAuthInfo(clientId, config.instanceId, config.accessKeyId, config.accessKeySecret);
|
|
63
|
+
this.client = mqtt_1.default.connect(`mqtts://${config.host}:8883`, {
|
|
64
|
+
clientId,
|
|
65
|
+
username,
|
|
66
|
+
password,
|
|
67
|
+
reconnectPeriod: 1000,
|
|
68
|
+
connectTimeout: 30000,
|
|
69
|
+
keepalive: 120,
|
|
70
|
+
resubscribe: true,
|
|
71
|
+
});
|
|
72
|
+
this.client.on("message", async (topic, message) => {
|
|
73
|
+
await this.onMessage(topic, message);
|
|
74
|
+
});
|
|
75
|
+
this.client.on("connect", async () => {
|
|
76
|
+
await this.onConnect();
|
|
77
|
+
});
|
|
78
|
+
this.client.on("close", () => {
|
|
79
|
+
if (this.hasConnectedOnce) {
|
|
80
|
+
this.logger.warn("ali.mqtt.connection.closed");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.logger.info("ali.mqtt.connection.closed.startup");
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
this.client.on("offline", () => {
|
|
87
|
+
if (this.hasConnectedOnce) {
|
|
88
|
+
this.logger.warn("ali.mqtt.connection.offline");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.logger.info("ali.mqtt.connection.offline.startup");
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
this.client.on("reconnect", () => {
|
|
95
|
+
if (this.hasConnectedOnce) {
|
|
96
|
+
this.logger.warn("ali.mqtt.connection.reconnecting");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.logger.info("ali.mqtt.connection.reconnecting.startup");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
this.client.on("end", () => {
|
|
103
|
+
this.logger.warn("ali.mqtt.connection.ended");
|
|
104
|
+
});
|
|
105
|
+
this.client.on("disconnect", (packet) => {
|
|
106
|
+
this.logger.warn({ packet }, "ali.mqtt.connection.disconnected");
|
|
107
|
+
});
|
|
108
|
+
this.client.on("error", async (e) => {
|
|
109
|
+
if (this.hasConnectedOnce) {
|
|
110
|
+
this.logger.error({ error: e }, "ali.mqtt.connection.error");
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.logger.warn({ error: e }, "ali.mqtt.connection.error.startup");
|
|
114
|
+
}
|
|
115
|
+
await this.onError();
|
|
116
|
+
});
|
|
117
|
+
await new Promise((resolve, reject) => {
|
|
118
|
+
const timeoutMs = 90000;
|
|
119
|
+
const timer = setTimeout(() => reject(new Error(`ali.mqtt.init.timeout.${timeoutMs}ms`)), timeoutMs);
|
|
120
|
+
this.client.once("connect", () => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve(1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async onError() {
|
|
127
|
+
this.logger.info("ali.mqtt.error.handled");
|
|
128
|
+
}
|
|
129
|
+
async onMessage(topic, message) {
|
|
130
|
+
for (const [pattern, groupMap] of this.topicHandlerMap.entries()) {
|
|
131
|
+
if (!this.isTopicMatch(pattern, topic)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const matchValue = this.getMatchValue(pattern, topic);
|
|
135
|
+
for (const registration of groupMap.values()) {
|
|
136
|
+
await registration.handler({ topic, payload: message, match: matchValue });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async onConnect() {
|
|
141
|
+
this.hasConnectedOnce = true;
|
|
142
|
+
this.logger.info("ali.mqtt.connection.connected");
|
|
143
|
+
const topicQosMap = new Map();
|
|
144
|
+
for (const [topicPattern, groupMap] of this.topicHandlerMap.entries()) {
|
|
145
|
+
let maxQos = 0;
|
|
146
|
+
for (const registration of groupMap.values()) {
|
|
147
|
+
maxQos = Math.max(maxQos, registration.qos);
|
|
148
|
+
}
|
|
149
|
+
topicQosMap.set(topicPattern, maxQos);
|
|
150
|
+
}
|
|
151
|
+
for (const [topic, qos] of topicQosMap.entries()) {
|
|
152
|
+
await this.subscribe(topic, { qos });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async subscribe(topic, opts) {
|
|
156
|
+
await new Promise((resolve, reject) => {
|
|
157
|
+
this.client.subscribe(topic, opts, (err) => {
|
|
158
|
+
if (err) {
|
|
159
|
+
reject(err);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
resolve();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
isTopicMatch(pattern, topic) {
|
|
167
|
+
if (!this.isWildcardPattern(pattern)) {
|
|
168
|
+
return pattern === topic;
|
|
169
|
+
}
|
|
170
|
+
return mqtt_pattern_1.default.matches(pattern, topic);
|
|
171
|
+
}
|
|
172
|
+
isWildcardPattern(pattern) {
|
|
173
|
+
return pattern.includes("+") || pattern.includes("#");
|
|
174
|
+
}
|
|
175
|
+
getMatchValue(pattern, topic) {
|
|
176
|
+
if (!this.isWildcardPattern(pattern)) {
|
|
177
|
+
return topic;
|
|
178
|
+
}
|
|
179
|
+
const plusIdx = pattern.indexOf("+");
|
|
180
|
+
const hashIdx = pattern.indexOf("#");
|
|
181
|
+
const idxList = [plusIdx, hashIdx].filter((idx) => idx >= 0);
|
|
182
|
+
if (idxList.length === 0) {
|
|
183
|
+
return topic;
|
|
184
|
+
}
|
|
185
|
+
const firstWildcardIndex = Math.min(...idxList);
|
|
186
|
+
const prefix = pattern.slice(0, firstWildcardIndex);
|
|
187
|
+
return topic.startsWith(prefix) ? (topic.slice(prefix.length) || "") : topic;
|
|
188
|
+
}
|
|
189
|
+
async publish(topic, message, opts) {
|
|
190
|
+
await this.client.publish(topic, message, opts);
|
|
191
|
+
}
|
|
192
|
+
async registerService(service) {
|
|
193
|
+
return this.subscribeHandler({
|
|
194
|
+
id: service.id,
|
|
195
|
+
topicPattern: service.topic,
|
|
196
|
+
qos: service.qos,
|
|
197
|
+
handler: async ({ topic, payload, match }) => service.callback(topic, payload, match),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async unregisterService(id) {
|
|
201
|
+
await this.unsubscribeHandler(id);
|
|
202
|
+
}
|
|
203
|
+
async subscribeHandler(registration) {
|
|
204
|
+
registration.id = registration.id || (0, crypto_1.randomUUID)();
|
|
205
|
+
this.handlerMap.set(registration.id, registration);
|
|
206
|
+
const groupMap = this.topicHandlerMap.get(registration.topicPattern);
|
|
207
|
+
const isNewTopic = !groupMap;
|
|
208
|
+
if (groupMap) {
|
|
209
|
+
groupMap.set(registration.id, registration);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const gMap = new Map();
|
|
213
|
+
gMap.set(registration.id, registration);
|
|
214
|
+
this.topicHandlerMap.set(registration.topicPattern, gMap);
|
|
215
|
+
}
|
|
216
|
+
if (isNewTopic) {
|
|
217
|
+
await this.subscribe(registration.topicPattern, { qos: registration.qos });
|
|
218
|
+
}
|
|
219
|
+
return registration.id;
|
|
220
|
+
}
|
|
221
|
+
async unsubscribeHandler(id) {
|
|
222
|
+
const registration = this.handlerMap.get(id);
|
|
223
|
+
if (!registration) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const groupMap = this.topicHandlerMap.get(registration.topicPattern);
|
|
227
|
+
if (groupMap) {
|
|
228
|
+
groupMap.delete(id);
|
|
229
|
+
if (groupMap.size === 0) {
|
|
230
|
+
this.topicHandlerMap.delete(registration.topicPattern);
|
|
231
|
+
this.client.unsubscribe(registration.topicPattern);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.handlerMap.delete(id);
|
|
235
|
+
}
|
|
236
|
+
async clearService() {
|
|
237
|
+
for (const topic of this.topicHandlerMap.keys()) {
|
|
238
|
+
this.client.unsubscribe(topic);
|
|
239
|
+
}
|
|
240
|
+
this.handlerMap.clear();
|
|
241
|
+
this.topicHandlerMap.clear();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.AliMqttService = AliMqttService;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as aws from "aws-iot-device-sdk";
|
|
2
|
+
import mqtt from "mqtt";
|
|
3
|
+
import { AwsMqttConfig, MqttTopicRunner } from "./mqtt.type";
|
|
4
|
+
type MqttQos = 0 | 1 | 2;
|
|
5
|
+
interface MqttMessageContext {
|
|
6
|
+
topic: string;
|
|
7
|
+
payload: Buffer;
|
|
8
|
+
match?: string;
|
|
9
|
+
}
|
|
10
|
+
interface MqttHandlerRegistration {
|
|
11
|
+
id?: string;
|
|
12
|
+
topicPattern: string;
|
|
13
|
+
qos: MqttQos;
|
|
14
|
+
handler: (ctx: MqttMessageContext) => Promise<void> | void;
|
|
15
|
+
}
|
|
16
|
+
export declare class AwsMqttService {
|
|
17
|
+
client: aws.device;
|
|
18
|
+
private handlerMap;
|
|
19
|
+
private topicHandlerMap;
|
|
20
|
+
private hasConnectedOnce;
|
|
21
|
+
private readonly logger;
|
|
22
|
+
init(config: AwsMqttConfig): Promise<void>;
|
|
23
|
+
private onError;
|
|
24
|
+
private onMessage;
|
|
25
|
+
private onConnect;
|
|
26
|
+
private subscribe;
|
|
27
|
+
private isTopicMatch;
|
|
28
|
+
private isWildcardPattern;
|
|
29
|
+
private getMatchValue;
|
|
30
|
+
publish(topic: string, message: string | Buffer, opts: mqtt.IClientPublishOptions): Promise<void>;
|
|
31
|
+
registerService(service: MqttTopicRunner): Promise<string>;
|
|
32
|
+
unregisterService(id: string): Promise<void>;
|
|
33
|
+
subscribeHandler(registration: MqttHandlerRegistration): Promise<string>;
|
|
34
|
+
unsubscribeHandler(id: string): Promise<void>;
|
|
35
|
+
clearService(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.AwsMqttService = void 0;
|
|
40
|
+
const crypto_1 = require("crypto");
|
|
41
|
+
const aws = __importStar(require("aws-iot-device-sdk"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const mqtt_pattern_1 = __importDefault(require("mqtt-pattern"));
|
|
44
|
+
const logger_1 = require("../logger");
|
|
45
|
+
class AwsMqttService {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.handlerMap = new Map();
|
|
48
|
+
this.topicHandlerMap = new Map();
|
|
49
|
+
this.hasConnectedOnce = false;
|
|
50
|
+
this.logger = (0, logger_1.createLogger)(AwsMqttService.name);
|
|
51
|
+
}
|
|
52
|
+
async init(config) {
|
|
53
|
+
const pathPrefix = process.cwd();
|
|
54
|
+
this.client = new aws.device({
|
|
55
|
+
keyPath: path.resolve(pathPrefix, config.keyPath),
|
|
56
|
+
certPath: path.resolve(pathPrefix, config.certPath),
|
|
57
|
+
caPath: path.resolve(pathPrefix, config.caPath),
|
|
58
|
+
clientId: "mp-iot-service_" + (0, crypto_1.randomUUID)(),
|
|
59
|
+
host: config.host,
|
|
60
|
+
});
|
|
61
|
+
this.client.on("message", async (topic, message) => {
|
|
62
|
+
await this.onMessage(topic, message);
|
|
63
|
+
});
|
|
64
|
+
this.client.on("connect", async () => {
|
|
65
|
+
await this.onConnect();
|
|
66
|
+
});
|
|
67
|
+
this.client.on("close", () => {
|
|
68
|
+
if (this.hasConnectedOnce) {
|
|
69
|
+
this.logger.warn("aws.mqtt.connection.closed");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.logger.info("aws.mqtt.connection.closed.startup");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
this.client.on("offline", () => {
|
|
76
|
+
if (this.hasConnectedOnce) {
|
|
77
|
+
this.logger.warn("aws.mqtt.connection.offline");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.logger.info("aws.mqtt.connection.offline.startup");
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.client.on("reconnect", () => {
|
|
84
|
+
if (this.hasConnectedOnce) {
|
|
85
|
+
this.logger.warn("aws.mqtt.connection.reconnecting");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.logger.info("aws.mqtt.connection.reconnecting.startup");
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.client.on("close", () => {
|
|
92
|
+
this.logger.warn("aws.mqtt.connection.ended");
|
|
93
|
+
});
|
|
94
|
+
this.client.on("error", async (e) => {
|
|
95
|
+
if (this.hasConnectedOnce) {
|
|
96
|
+
this.logger.error({ error: e }, "aws.mqtt.connection.error");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.logger.warn({ error: e }, "aws.mqtt.connection.error.startup");
|
|
100
|
+
}
|
|
101
|
+
await this.onError();
|
|
102
|
+
});
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
const timeoutMs = 90000;
|
|
105
|
+
const timer = setTimeout(() => reject(new Error(`aws.mqtt.init.timeout.${timeoutMs}ms`)), timeoutMs);
|
|
106
|
+
this.client.once("connect", () => {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
resolve(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async onError() {
|
|
113
|
+
this.logger.info("aws.mqtt.error.handled");
|
|
114
|
+
}
|
|
115
|
+
async onMessage(topic, message) {
|
|
116
|
+
for (const [pattern, groupMap] of this.topicHandlerMap.entries()) {
|
|
117
|
+
if (!this.isTopicMatch(pattern, topic)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const matchValue = this.getMatchValue(pattern, topic);
|
|
121
|
+
for (const registration of groupMap.values()) {
|
|
122
|
+
await registration.handler({ topic, payload: message, match: matchValue });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async onConnect() {
|
|
127
|
+
this.hasConnectedOnce = true;
|
|
128
|
+
this.logger.info("aws.mqtt.connection.connected");
|
|
129
|
+
const topicQosMap = new Map();
|
|
130
|
+
for (const [topicPattern, groupMap] of this.topicHandlerMap.entries()) {
|
|
131
|
+
let maxQos = 0;
|
|
132
|
+
for (const registration of groupMap.values()) {
|
|
133
|
+
maxQos = Math.max(maxQos, registration.qos);
|
|
134
|
+
}
|
|
135
|
+
topicQosMap.set(topicPattern, maxQos);
|
|
136
|
+
}
|
|
137
|
+
for (const [topic, qos] of topicQosMap.entries()) {
|
|
138
|
+
await this.subscribe(topic, { qos });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async subscribe(topic, opts) {
|
|
142
|
+
await new Promise((resolve, reject) => {
|
|
143
|
+
this.client.subscribe(topic, opts, (err) => {
|
|
144
|
+
if (err) {
|
|
145
|
+
reject(err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
isTopicMatch(pattern, topic) {
|
|
153
|
+
if (!this.isWildcardPattern(pattern)) {
|
|
154
|
+
return pattern === topic;
|
|
155
|
+
}
|
|
156
|
+
return mqtt_pattern_1.default.matches(pattern, topic);
|
|
157
|
+
}
|
|
158
|
+
isWildcardPattern(pattern) {
|
|
159
|
+
return pattern.includes("+") || pattern.includes("#");
|
|
160
|
+
}
|
|
161
|
+
getMatchValue(pattern, topic) {
|
|
162
|
+
if (!this.isWildcardPattern(pattern)) {
|
|
163
|
+
return topic;
|
|
164
|
+
}
|
|
165
|
+
const plusIdx = pattern.indexOf("+");
|
|
166
|
+
const hashIdx = pattern.indexOf("#");
|
|
167
|
+
const idxList = [plusIdx, hashIdx].filter((idx) => idx >= 0);
|
|
168
|
+
if (idxList.length === 0) {
|
|
169
|
+
return topic;
|
|
170
|
+
}
|
|
171
|
+
const firstWildcardIndex = Math.min(...idxList);
|
|
172
|
+
const prefix = pattern.slice(0, firstWildcardIndex);
|
|
173
|
+
return topic.startsWith(prefix) ? (topic.slice(prefix.length) || "") : topic;
|
|
174
|
+
}
|
|
175
|
+
async publish(topic, message, opts) {
|
|
176
|
+
await new Promise((resolve, reject) => {
|
|
177
|
+
this.client.publish(topic, message, opts, (err) => {
|
|
178
|
+
if (err) {
|
|
179
|
+
reject(err);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
resolve();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async registerService(service) {
|
|
187
|
+
return this.subscribeHandler({
|
|
188
|
+
id: service.id,
|
|
189
|
+
topicPattern: service.topic,
|
|
190
|
+
qos: service.qos,
|
|
191
|
+
handler: async ({ topic, payload, match }) => service.callback(topic, payload, match),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async unregisterService(id) {
|
|
195
|
+
await this.unsubscribeHandler(id);
|
|
196
|
+
}
|
|
197
|
+
async subscribeHandler(registration) {
|
|
198
|
+
registration.id = registration.id || (0, crypto_1.randomUUID)();
|
|
199
|
+
this.handlerMap.set(registration.id, registration);
|
|
200
|
+
const groupMap = this.topicHandlerMap.get(registration.topicPattern);
|
|
201
|
+
const isNewTopic = !groupMap;
|
|
202
|
+
if (groupMap) {
|
|
203
|
+
groupMap.set(registration.id, registration);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const gMap = new Map();
|
|
207
|
+
gMap.set(registration.id, registration);
|
|
208
|
+
this.topicHandlerMap.set(registration.topicPattern, gMap);
|
|
209
|
+
}
|
|
210
|
+
if (isNewTopic) {
|
|
211
|
+
await this.subscribe(registration.topicPattern, { qos: registration.qos });
|
|
212
|
+
}
|
|
213
|
+
return registration.id;
|
|
214
|
+
}
|
|
215
|
+
async unsubscribeHandler(id) {
|
|
216
|
+
const registration = this.handlerMap.get(id);
|
|
217
|
+
if (!registration) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const groupMap = this.topicHandlerMap.get(registration.topicPattern);
|
|
221
|
+
if (groupMap) {
|
|
222
|
+
groupMap.delete(id);
|
|
223
|
+
if (groupMap.size === 0) {
|
|
224
|
+
this.topicHandlerMap.delete(registration.topicPattern);
|
|
225
|
+
this.client.unsubscribe(registration.topicPattern);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
this.handlerMap.delete(id);
|
|
229
|
+
}
|
|
230
|
+
async clearService() {
|
|
231
|
+
for (const topic of this.topicHandlerMap.keys()) {
|
|
232
|
+
this.client.unsubscribe(topic);
|
|
233
|
+
}
|
|
234
|
+
this.handlerMap.clear();
|
|
235
|
+
this.topicHandlerMap.clear();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
exports.AwsMqttService = AwsMqttService;
|
package/dist/mqtt/mqtt.type.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export interface AliyunMqttConfig {
|
|
2
|
+
host: string;
|
|
3
|
+
instanceId: string;
|
|
4
|
+
accessKeyId: string;
|
|
5
|
+
accessKeySecret: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AwsMqttConfig {
|
|
8
|
+
host: string;
|
|
9
|
+
keyPath: string;
|
|
10
|
+
certPath: string;
|
|
11
|
+
caPath: string;
|
|
12
|
+
}
|
|
1
13
|
export declare class MqttTopicRunner {
|
|
2
14
|
id?: string;
|
|
3
15
|
name?: string;
|
package/dist/pg.service.d.ts
CHANGED
|
@@ -5,13 +5,14 @@ export declare class PGClientConfig {
|
|
|
5
5
|
database: string;
|
|
6
6
|
username: string;
|
|
7
7
|
password: string;
|
|
8
|
-
ssl?:
|
|
8
|
+
ssl?: boolean;
|
|
9
9
|
max?: number;
|
|
10
10
|
}
|
|
11
11
|
export declare class PGClient {
|
|
12
12
|
private pool;
|
|
13
|
+
private readonly logger;
|
|
13
14
|
init(config: PGClientConfig): Promise<void>;
|
|
14
|
-
query(sql: string, params?:
|
|
15
|
-
queryOne(sql: string, params?:
|
|
16
|
-
transaction(callback: (client: pg.PoolClient) =>
|
|
15
|
+
query<T = any>(sql: string, params?: readonly unknown[]): Promise<T[]>;
|
|
16
|
+
queryOne<T = any>(sql: string, params?: readonly unknown[]): Promise<T>;
|
|
17
|
+
transaction<T>(callback: (client: pg.PoolClient) => Promise<T> | T): Promise<T>;
|
|
17
18
|
}
|
package/dist/pg.service.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.PGClient = exports.PGClientConfig = void 0;
|
|
37
37
|
const pg = __importStar(require("pg"));
|
|
38
38
|
const humps_1 = require("humps");
|
|
39
|
+
const logger_1 = require("./logger");
|
|
39
40
|
class PGClientConfig {
|
|
40
41
|
constructor() {
|
|
41
42
|
this.ssl = false;
|
|
@@ -44,6 +45,9 @@ class PGClientConfig {
|
|
|
44
45
|
}
|
|
45
46
|
exports.PGClientConfig = PGClientConfig;
|
|
46
47
|
class PGClient {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.logger = (0, logger_1.createLogger)("PGClient");
|
|
50
|
+
}
|
|
47
51
|
async init(config) {
|
|
48
52
|
const pgConfig = {
|
|
49
53
|
host: config.host,
|
|
@@ -62,17 +66,18 @@ class PGClient {
|
|
|
62
66
|
const client = await this.pool.connect();
|
|
63
67
|
await client.query("select now()");
|
|
64
68
|
await client.release();
|
|
69
|
+
this.logger.debug("pg.init.ping.ok");
|
|
65
70
|
}
|
|
66
71
|
async query(sql, params) {
|
|
67
72
|
let result = [];
|
|
68
73
|
let error;
|
|
69
74
|
const client = await this.pool.connect();
|
|
70
75
|
try {
|
|
71
|
-
const { rows } = await client.query(sql, params);
|
|
76
|
+
const { rows } = params ? await client.query(sql, [...params]) : await client.query(sql);
|
|
72
77
|
result = (0, humps_1.camelizeKeys)(rows);
|
|
73
78
|
}
|
|
74
79
|
catch (e) {
|
|
75
|
-
|
|
80
|
+
this.logger.error({ error: e }, "pg.query.failed");
|
|
76
81
|
error = e;
|
|
77
82
|
}
|
|
78
83
|
finally {
|
|
@@ -87,11 +92,11 @@ class PGClient {
|
|
|
87
92
|
let result;
|
|
88
93
|
const client = await this.pool.connect();
|
|
89
94
|
try {
|
|
90
|
-
const { rows } = await client.query(sql, params);
|
|
91
|
-
result = (0, humps_1.camelizeKeys)(rows[0]) || {};
|
|
95
|
+
const { rows } = params ? await client.query(sql, [...params]) : await client.query(sql);
|
|
96
|
+
result = ((0, humps_1.camelizeKeys)(rows[0]) || {});
|
|
92
97
|
}
|
|
93
98
|
catch (e) {
|
|
94
|
-
|
|
99
|
+
this.logger.error({ error: e }, "pg.query_one.failed");
|
|
95
100
|
throw new Error(String(e));
|
|
96
101
|
}
|
|
97
102
|
finally {
|
|
@@ -103,12 +108,13 @@ class PGClient {
|
|
|
103
108
|
const client = await this.pool.connect();
|
|
104
109
|
await client.query("BEGIN");
|
|
105
110
|
try {
|
|
106
|
-
await callback(client);
|
|
111
|
+
const result = await callback(client);
|
|
107
112
|
await client.query("COMMIT");
|
|
108
113
|
await client.release();
|
|
114
|
+
return result;
|
|
109
115
|
}
|
|
110
116
|
catch (e) {
|
|
111
|
-
|
|
117
|
+
this.logger.error({ error: e }, "pg.transaction.failed");
|
|
112
118
|
await client.query("ROLLBACK");
|
|
113
119
|
await client.release();
|
|
114
120
|
throw e;
|
package/dist/redis.service.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import Redis from "ioredis";
|
|
2
|
+
import { RedisOptions } from "ioredis";
|
|
2
3
|
export declare class RedisClientConfig {
|
|
3
4
|
host: string;
|
|
4
5
|
port: number;
|
|
5
6
|
db: number;
|
|
6
7
|
username?: string;
|
|
7
8
|
password?: string;
|
|
8
|
-
tls?:
|
|
9
|
+
tls?: RedisOptions["tls"];
|
|
9
10
|
keyPrefix?: string;
|
|
10
11
|
cluster?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare class RedisClient {
|
|
13
14
|
client: Redis;
|
|
14
|
-
private
|
|
15
|
+
private readonly logger;
|
|
15
16
|
init(config: RedisClientConfig): Promise<void>;
|
|
16
|
-
set(key: string, value:
|
|
17
|
+
set(key: string, value: string | number | Buffer, expire?: number): Promise<void>;
|
|
17
18
|
get(key: string): Promise<string | null>;
|
|
18
|
-
setJsonValue(key: string, value:
|
|
19
|
-
getJsonValue(key: string): Promise<
|
|
19
|
+
setJsonValue<T>(key: string, value: T): Promise<"OK" | null>;
|
|
20
|
+
getJsonValue<T>(key: string): Promise<T | null>;
|
|
20
21
|
exists(key: string): Promise<number>;
|
|
21
22
|
}
|
package/dist/redis.service.js
CHANGED
|
@@ -5,42 +5,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.RedisClient = exports.RedisClientConfig = void 0;
|
|
7
7
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
+
const logger_1 = require("./logger");
|
|
8
9
|
class RedisClientConfig {
|
|
9
10
|
}
|
|
10
11
|
exports.RedisClientConfig = RedisClientConfig;
|
|
11
12
|
class RedisClient {
|
|
12
13
|
constructor() {
|
|
13
|
-
this.
|
|
14
|
+
this.logger = (0, logger_1.createLogger)("RedisClient");
|
|
14
15
|
}
|
|
15
16
|
async init(config) {
|
|
16
|
-
this.config = config;
|
|
17
17
|
try {
|
|
18
|
-
|
|
18
|
+
const redisOptions = {
|
|
19
19
|
host: config.host,
|
|
20
20
|
port: config.port,
|
|
21
21
|
db: config.db,
|
|
22
22
|
username: config.username,
|
|
23
23
|
password: config.password,
|
|
24
24
|
disableClientInfo: true,
|
|
25
|
-
tls: config.tls || false,
|
|
26
25
|
reconnectOnError: (err) => {
|
|
27
|
-
|
|
26
|
+
this.logger.warn({ error: err }, "redis.reconnect_on_error");
|
|
28
27
|
return true;
|
|
29
28
|
},
|
|
30
29
|
retryStrategy: (times) => {
|
|
31
30
|
const delay = Math.min(times * 50, 2000);
|
|
32
|
-
|
|
31
|
+
this.logger.info({ delayMs: delay, retryTimes: times }, "redis.retry_scheduled");
|
|
33
32
|
return delay;
|
|
34
33
|
},
|
|
35
|
-
}
|
|
34
|
+
};
|
|
35
|
+
if (config.tls) {
|
|
36
|
+
redisOptions.tls = config.tls;
|
|
37
|
+
}
|
|
38
|
+
this.client = new ioredis_1.default(redisOptions);
|
|
36
39
|
this.client.on("error", (err) => {
|
|
37
|
-
|
|
40
|
+
this.logger.error({ error: err }, "redis.client.error");
|
|
38
41
|
});
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
await this.client.ping();
|
|
43
|
+
this.logger.debug("redis.ping.ok");
|
|
41
44
|
}
|
|
42
45
|
catch (e) {
|
|
43
|
-
|
|
46
|
+
this.logger.error({ error: e }, "redis.init.failed");
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
async set(key, value, expire) {
|
|
@@ -49,7 +52,7 @@ class RedisClient {
|
|
|
49
52
|
await client.multi().set(key, value).expire(key, expire || 300).exec();
|
|
50
53
|
}
|
|
51
54
|
catch (e) {
|
|
52
|
-
|
|
55
|
+
this.logger.error({ error: e, key }, "redis.set.failed");
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
async get(key) {
|
|
@@ -60,14 +63,16 @@ class RedisClient {
|
|
|
60
63
|
}
|
|
61
64
|
async getJsonValue(key) {
|
|
62
65
|
const s = await this.client.get(key);
|
|
63
|
-
|
|
66
|
+
if (!s) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
64
69
|
try {
|
|
65
|
-
|
|
70
|
+
return JSON.parse(s);
|
|
66
71
|
}
|
|
67
72
|
catch (e) {
|
|
68
|
-
|
|
73
|
+
this.logger.error({ error: e, key }, "redis.get_json.parse_failed");
|
|
74
|
+
return null;
|
|
69
75
|
}
|
|
70
|
-
return r;
|
|
71
76
|
}
|
|
72
77
|
async exists(key) {
|
|
73
78
|
return await this.client.exists(key);
|
package/dist/rmq.service.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as amqp from "amqplib";
|
|
2
2
|
import { Channel } from "amqplib";
|
|
3
|
+
export type ConsumeMessage = amqp.ConsumeMessage;
|
|
4
|
+
export type RmqConsumeCallback = (data: ConsumeMessage, channel: Channel) => Promise<void> | void;
|
|
3
5
|
export declare class QueueItem {
|
|
4
6
|
queue: string;
|
|
5
7
|
prefetch?: number;
|
|
@@ -39,6 +41,6 @@ export declare class RmqService {
|
|
|
39
41
|
private restoreConsumers;
|
|
40
42
|
private startConsume;
|
|
41
43
|
private closeAndClearChannels;
|
|
42
|
-
publishToExchange(exchange: string, routingKey: string | undefined, message:
|
|
43
|
-
consumer(queue: string, callback:
|
|
44
|
+
publishToExchange(exchange: string, routingKey: string | undefined, message: unknown, options?: amqp.Options.Publish): Promise<void>;
|
|
45
|
+
consumer(queue: string, callback: RmqConsumeCallback): Promise<void>;
|
|
44
46
|
}
|
package/dist/sls.service.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export interface SLSServiceConfig {
|
|
|
3
3
|
accessKeyId: string;
|
|
4
4
|
accessKeySecret: string;
|
|
5
5
|
}
|
|
6
|
+
export interface SlsLogClient {
|
|
7
|
+
postLogStoreLogs: (projectName: string, logstoreName: string, logGroup: unknown) => Promise<unknown>;
|
|
8
|
+
}
|
|
6
9
|
export declare class SLSClient {
|
|
7
|
-
client:
|
|
10
|
+
client: SlsLogClient;
|
|
8
11
|
init(config: SLSServiceConfig): Promise<void>;
|
|
9
12
|
}
|
package/dist/test/smoke.test.js
CHANGED
|
@@ -13,6 +13,8 @@ const index_1 = require("../index");
|
|
|
13
13
|
const redis = new index_1.RedisClient();
|
|
14
14
|
const rmq = new index_1.RmqService();
|
|
15
15
|
const sls = new index_1.SLSClient();
|
|
16
|
+
const awsMqtt = new index_1.AwsMqttService();
|
|
17
|
+
const aliMqtt = new index_1.AliMqttService();
|
|
16
18
|
const mqtt = new index_1.EmqxMqttService();
|
|
17
19
|
const mqttCompat = new index_1.EMQXMqttService();
|
|
18
20
|
const runner = new index_1.MqttTopicRunner();
|
|
@@ -20,6 +22,8 @@ const index_1 = require("../index");
|
|
|
20
22
|
strict_1.default.ok(redis);
|
|
21
23
|
strict_1.default.ok(rmq);
|
|
22
24
|
strict_1.default.ok(sls);
|
|
25
|
+
strict_1.default.ok(awsMqtt);
|
|
26
|
+
strict_1.default.ok(aliMqtt);
|
|
23
27
|
strict_1.default.ok(mqtt);
|
|
24
28
|
strict_1.default.ok(mqttCompat);
|
|
25
29
|
strict_1.default.ok(runner.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mango-power/node-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Shared node toolkit for mp services",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@alicloud/log": "^1.2.6",
|
|
29
29
|
"amqplib": "^0.10.9",
|
|
30
|
+
"aws-iot-device-sdk": "^2.2.16",
|
|
30
31
|
"humps": "^2.0.1",
|
|
31
32
|
"ioredis": "^5.8.1",
|
|
32
33
|
"mqtt": "^5.14.1",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/amqplib": "^0.10.8",
|
|
39
|
+
"@types/aws-iot-device-sdk": "^2.2.8",
|
|
38
40
|
"@types/humps": "^2.0.6",
|
|
39
41
|
"@types/pg": "^8.15.6",
|
|
40
42
|
"@types/node": "^24.11.0",
|