mqtt-plus 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +36 -0
- package/dst-stage1/mqtt-plus-base.d.ts +2 -2
- package/dst-stage1/mqtt-plus-base.js +11 -0
- package/dst-stage1/mqtt-plus-codec.js +1 -1
- package/dst-stage1/mqtt-plus-event.d.ts +12 -0
- package/dst-stage1/mqtt-plus-event.js +9 -2
- package/dst-stage1/mqtt-plus-service.js +10 -3
- package/dst-stage2/mqtt-plus.cjs.js +27 -5
- package/dst-stage2/mqtt-plus.esm.js +27 -5
- package/dst-stage2/mqtt-plus.umd.js +9 -9
- package/package.json +1 -1
- package/src/mqtt-plus-base.ts +14 -2
- package/src/mqtt-plus-codec.ts +2 -2
- package/src/mqtt-plus-event.ts +20 -3
- package/src/mqtt-plus-service.ts +10 -3
- package/tst/mqtt-plus.spec.ts +60 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
ChangeLog
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
0.9.17 (2026-01-25)
|
|
6
|
+
-------------------
|
|
7
|
+
|
|
8
|
+
- IMPROVEMENT: add Dry-Run mode will null MQTT client
|
|
9
|
+
- IMPROVEMENT: add Dry-Run mode for emit() to generate last-will message
|
|
10
|
+
|
|
11
|
+
0.9.16 (2026-01-24)
|
|
12
|
+
-------------------
|
|
13
|
+
|
|
14
|
+
- CLEANUP: cleanup subscription handling
|
|
15
|
+
- UPDATE: upgrade NPM dependencies
|
|
16
|
+
|
|
17
|
+
0.9.15 (2026-01-24)
|
|
18
|
+
-------------------
|
|
19
|
+
|
|
20
|
+
- IMPROVEMENT: provide unpkg.com sample
|
|
21
|
+
- IMPROVEMENT: provide esm/cjs/umd import sub-paths
|
|
22
|
+
|
|
23
|
+
0.9.14 (2026-01-24)
|
|
24
|
+
-------------------
|
|
25
|
+
|
|
26
|
+
- REFACTORING: change external API from Buffer to Uint8Array to better support browsers
|
|
27
|
+
- IMPROVEMENT: add "dev" STX target for convenient development
|
|
28
|
+
|
|
29
|
+
0.9.14 (2026-01-24)
|
|
30
|
+
-------------------
|
|
31
|
+
|
|
32
|
+
- IMPROVEMENT: use Base64 for encoding buffers in JSON encoding
|
|
33
|
+
- IMPROVEMENT: switch from "cbor" to "cbor2" for CBOR encoding
|
|
34
|
+
|
|
35
|
+
[...]
|
|
36
|
+
|
|
37
|
+
0.9.0 (2026-01-05)
|
|
38
|
+
------------------
|
|
39
|
+
|
|
40
|
+
(first rough cut of library)
|
|
41
|
+
|
package/README.md
CHANGED
|
@@ -286,10 +286,20 @@ The **MQTT+** API provides the following methods:
|
|
|
286
286
|
receiver?: string,
|
|
287
287
|
options?: MQTT::IClientSubscribeOptions
|
|
288
288
|
}): void
|
|
289
|
+
emit({
|
|
290
|
+
event: string,
|
|
291
|
+
params: any[],
|
|
292
|
+
receiver?: string,
|
|
293
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
294
|
+
dry: true
|
|
295
|
+
}): { topic: string, payload: Buffer, options: IClientPublishOptions }
|
|
289
296
|
|
|
290
297
|
Emit an event to all subscribers or a specific subscriber ("fire and forget").
|
|
291
298
|
The optional `receiver` directs the event to a specific subscriber only.
|
|
292
299
|
The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
|
|
300
|
+
The optional `dry` flag, when set to `true`, returns the publish information
|
|
301
|
+
(`topic`, `payload`, `options`) instead of actually publishing to the MQTT broker.
|
|
302
|
+
This is useful for generating MQTT "last will" messages (see example below).
|
|
293
303
|
|
|
294
304
|
The remote `subscribe()` `callback` is called with `params` and its
|
|
295
305
|
return value is silently ignored.
|
|
@@ -297,6 +307,32 @@ The **MQTT+** API provides the following methods:
|
|
|
297
307
|
Internally, publishes to the MQTT topic by `topicMake(event, "event-emission", peerId)`
|
|
298
308
|
(default: `${event}/event-emission/any` or `${event}/event-emission/${peerId}`).
|
|
299
309
|
|
|
310
|
+
**Dry-Run Publishing for MQTT Last-Will:**
|
|
311
|
+
When you need to set up an MQTT "last will" message (automatically published
|
|
312
|
+
by the broker when a client disconnects *unexpectedly*), you can use `dry: true`
|
|
313
|
+
together with a `null` MQTT client:
|
|
314
|
+
|
|
315
|
+
type API = {
|
|
316
|
+
"example/connection": Event<(state: "open" | "close") => void>
|
|
317
|
+
[...]
|
|
318
|
+
}
|
|
319
|
+
const mqttpDry = new MQTTp<API>(null, { id: "my-client" })
|
|
320
|
+
const will = mqttpDry.emit({
|
|
321
|
+
dry: true,
|
|
322
|
+
event: "example/connection",
|
|
323
|
+
params: [ "close" ],
|
|
324
|
+
[...]
|
|
325
|
+
})
|
|
326
|
+
mqttpDry.destroy()
|
|
327
|
+
const mqtt = MQTT.connect("[...]", {
|
|
328
|
+
will: {
|
|
329
|
+
topic: will.topic,
|
|
330
|
+
payload: will.payload,
|
|
331
|
+
qos: will.options.qos
|
|
332
|
+
},
|
|
333
|
+
[...]
|
|
334
|
+
})
|
|
335
|
+
|
|
300
336
|
- **Service Call**:<br/>
|
|
301
337
|
|
|
302
338
|
/* (simplified TypeScript API method signature) */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { MqttClient, IClientSubscribeOptions } from "mqtt";
|
|
1
|
+
import type { MqttClient, IClientSubscribeOptions } from "mqtt";
|
|
2
2
|
import { APISchema } from "./mqtt-plus-api";
|
|
3
3
|
import { MsgTrait } from "./mqtt-plus-msg";
|
|
4
4
|
import { APIOptions } from "./mqtt-plus-options";
|
|
5
5
|
export declare class BaseTrait<T extends APISchema = APISchema> extends MsgTrait<T> {
|
|
6
6
|
protected mqtt: MqttClient;
|
|
7
7
|
private _messageHandler;
|
|
8
|
-
constructor(mqtt: MqttClient, options?: Partial<APIOptions>);
|
|
8
|
+
constructor(mqtt: MqttClient | null, options?: Partial<APIOptions>);
|
|
9
9
|
destroy(): void;
|
|
10
10
|
protected _subscribeTopic(topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
|
|
11
11
|
protected _unsubscribeTopic(topic: string): Promise<void>;
|
|
@@ -27,6 +27,17 @@ export class BaseTrait extends MsgTrait {
|
|
|
27
27
|
/* construct API class */
|
|
28
28
|
constructor(mqtt, options = {}) {
|
|
29
29
|
super(options);
|
|
30
|
+
/* optionally provide a fake proxy for the MQTT client
|
|
31
|
+
(mainly for using emit({ ..., dry: true }) to just make MQTT "last will") */
|
|
32
|
+
if (mqtt === null)
|
|
33
|
+
mqtt = new Proxy({}, {
|
|
34
|
+
get(_target, prop, _receiver) {
|
|
35
|
+
if (prop === "isFakeProxy")
|
|
36
|
+
return true;
|
|
37
|
+
else
|
|
38
|
+
return (...args) => { };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
30
41
|
/* store MQTT client */
|
|
31
42
|
this.mqtt = mqtt;
|
|
32
43
|
/* hook into the MQTT message processing */
|
|
@@ -42,7 +42,7 @@ class JSONX {
|
|
|
42
42
|
: value);
|
|
43
43
|
}
|
|
44
44
|
static parse(json) {
|
|
45
|
-
return JSON.parse(json, (_, value) => value?.__Uint8Array
|
|
45
|
+
return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string"
|
|
46
46
|
? this.base64ToUint8Array(value.__Uint8Array)
|
|
47
47
|
: value);
|
|
48
48
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
1
2
|
import { IClientPublishOptions, IClientSubscribeOptions } from "mqtt";
|
|
2
3
|
import { APISchema, EventKeys } from "./mqtt-plus-api";
|
|
3
4
|
import type { WithInfo, InfoEvent } from "./mqtt-plus-info";
|
|
@@ -16,5 +17,16 @@ export declare class EventTrait<T extends APISchema = APISchema> extends BaseTra
|
|
|
16
17
|
receiver?: string;
|
|
17
18
|
options?: IClientPublishOptions;
|
|
18
19
|
}): void;
|
|
20
|
+
emit<K extends EventKeys<T> & string>(config: {
|
|
21
|
+
event: K;
|
|
22
|
+
params: Parameters<T[K]>;
|
|
23
|
+
receiver?: string;
|
|
24
|
+
options?: IClientPublishOptions;
|
|
25
|
+
dry: true;
|
|
26
|
+
}): {
|
|
27
|
+
topic: string;
|
|
28
|
+
payload: Buffer;
|
|
29
|
+
options: IClientPublishOptions;
|
|
30
|
+
};
|
|
19
31
|
protected _dispatchMessage(topic: string, parsed: any): void;
|
|
20
32
|
}
|
|
@@ -80,12 +80,14 @@ export class EventTrait extends BaseTrait {
|
|
|
80
80
|
let params;
|
|
81
81
|
let receiver;
|
|
82
82
|
let options = {};
|
|
83
|
+
let dry;
|
|
83
84
|
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
84
85
|
/* object-based API */
|
|
85
86
|
event = eventOrConfig.event;
|
|
86
87
|
params = eventOrConfig.params;
|
|
87
88
|
receiver = eventOrConfig.receiver;
|
|
88
89
|
options = eventOrConfig.options ?? {};
|
|
90
|
+
dry = eventOrConfig.dry;
|
|
89
91
|
}
|
|
90
92
|
else {
|
|
91
93
|
/* positional API */
|
|
@@ -99,8 +101,13 @@ export class EventTrait extends BaseTrait {
|
|
|
99
101
|
const message = this.codec.encode(request);
|
|
100
102
|
/* generate corresponding MQTT topic */
|
|
101
103
|
const topic = this.options.topicMake(event, "event-emission", receiver);
|
|
102
|
-
/*
|
|
103
|
-
|
|
104
|
+
/* produce result */
|
|
105
|
+
if (dry)
|
|
106
|
+
/* return publish information */
|
|
107
|
+
return { topic, payload: Buffer.from(message), options: { qos: 0, ...options } };
|
|
108
|
+
else
|
|
109
|
+
/* publish message to MQTT topic */
|
|
110
|
+
this.mqtt.publish(topic, Buffer.from(message), { qos: 0, ...options });
|
|
104
111
|
}
|
|
105
112
|
/* dispatch message (Event pattern handling) */
|
|
106
113
|
_dispatchMessage(topic, parsed) {
|
|
@@ -147,8 +147,14 @@ export class ServiceTrait extends EventTrait {
|
|
|
147
147
|
if (!this.responseSubscriptions.has(topic)) {
|
|
148
148
|
this.responseSubscriptions.set(topic, 0);
|
|
149
149
|
this.mqtt.subscribe(topic, options, (err) => {
|
|
150
|
-
if (err)
|
|
150
|
+
if (err) {
|
|
151
|
+
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
152
|
+
if (count > 1)
|
|
153
|
+
this.responseSubscriptions.set(topic, count - 1);
|
|
154
|
+
else
|
|
155
|
+
this.responseSubscriptions.delete(topic);
|
|
151
156
|
this.mqtt.emit("error", err);
|
|
157
|
+
}
|
|
152
158
|
});
|
|
153
159
|
}
|
|
154
160
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
@@ -163,8 +169,9 @@ export class ServiceTrait extends EventTrait {
|
|
|
163
169
|
return;
|
|
164
170
|
/* unsubscribe from MQTT topic and forget subscription */
|
|
165
171
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
if (count > 1)
|
|
173
|
+
this.responseSubscriptions.set(topic, count - 1);
|
|
174
|
+
else {
|
|
168
175
|
this.responseSubscriptions.delete(topic);
|
|
169
176
|
this.mqtt.unsubscribe(topic, (err) => {
|
|
170
177
|
if (err)
|
|
@@ -125,7 +125,7 @@ class JSONX {
|
|
|
125
125
|
return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
|
|
126
126
|
}
|
|
127
127
|
static parse(json) {
|
|
128
|
-
return JSON.parse(json, (_, value) => value?.__Uint8Array ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
128
|
+
return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
class Codec {
|
|
@@ -336,6 +336,16 @@ class BaseTrait extends MsgTrait {
|
|
|
336
336
|
/* construct API class */
|
|
337
337
|
constructor(mqtt, options = {}) {
|
|
338
338
|
super(options);
|
|
339
|
+
if (mqtt === null)
|
|
340
|
+
mqtt = new Proxy({}, {
|
|
341
|
+
get(_target, prop, _receiver) {
|
|
342
|
+
if (prop === "isFakeProxy")
|
|
343
|
+
return true;
|
|
344
|
+
else
|
|
345
|
+
return (...args) => {
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
});
|
|
339
349
|
this.mqtt = mqtt;
|
|
340
350
|
this._messageHandler = (topic, message, packet) => {
|
|
341
351
|
this._onMessage(topic, message, packet);
|
|
@@ -436,11 +446,13 @@ class EventTrait extends BaseTrait {
|
|
|
436
446
|
let params;
|
|
437
447
|
let receiver;
|
|
438
448
|
let options = {};
|
|
449
|
+
let dry;
|
|
439
450
|
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
440
451
|
event = eventOrConfig.event;
|
|
441
452
|
params = eventOrConfig.params;
|
|
442
453
|
receiver = eventOrConfig.receiver;
|
|
443
454
|
options = eventOrConfig.options ?? {};
|
|
455
|
+
dry = eventOrConfig.dry;
|
|
444
456
|
} else {
|
|
445
457
|
event = eventOrConfig;
|
|
446
458
|
params = args;
|
|
@@ -449,7 +461,10 @@ class EventTrait extends BaseTrait {
|
|
|
449
461
|
const request = this.msg.makeEventEmission(rid, event, params, this.options.id, receiver);
|
|
450
462
|
const message = this.codec.encode(request);
|
|
451
463
|
const topic = this.options.topicMake(event, "event-emission", receiver);
|
|
452
|
-
|
|
464
|
+
if (dry)
|
|
465
|
+
return { topic, payload: node_buffer.Buffer.from(message), options: { qos: 0, ...options } };
|
|
466
|
+
else
|
|
467
|
+
this.mqtt.publish(topic, node_buffer.Buffer.from(message), { qos: 0, ...options });
|
|
453
468
|
}
|
|
454
469
|
/* dispatch message (Event pattern handling) */
|
|
455
470
|
_dispatchMessage(topic, parsed) {
|
|
@@ -570,8 +585,14 @@ class ServiceTrait extends EventTrait {
|
|
|
570
585
|
if (!this.responseSubscriptions.has(topic)) {
|
|
571
586
|
this.responseSubscriptions.set(topic, 0);
|
|
572
587
|
this.mqtt.subscribe(topic, options, (err) => {
|
|
573
|
-
if (err)
|
|
588
|
+
if (err) {
|
|
589
|
+
const count2 = this.responseSubscriptions.get(topic) ?? 0;
|
|
590
|
+
if (count2 > 1)
|
|
591
|
+
this.responseSubscriptions.set(topic, count2 - 1);
|
|
592
|
+
else
|
|
593
|
+
this.responseSubscriptions.delete(topic);
|
|
574
594
|
this.mqtt.emit("error", err);
|
|
595
|
+
}
|
|
575
596
|
});
|
|
576
597
|
}
|
|
577
598
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
@@ -583,8 +604,9 @@ class ServiceTrait extends EventTrait {
|
|
|
583
604
|
if (!this.responseSubscriptions.has(topic))
|
|
584
605
|
return;
|
|
585
606
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
586
|
-
|
|
587
|
-
|
|
607
|
+
if (count > 1)
|
|
608
|
+
this.responseSubscriptions.set(topic, count - 1);
|
|
609
|
+
else {
|
|
588
610
|
this.responseSubscriptions.delete(topic);
|
|
589
611
|
this.mqtt.unsubscribe(topic, (err) => {
|
|
590
612
|
if (err)
|
|
@@ -107,7 +107,7 @@ class JSONX {
|
|
|
107
107
|
return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
|
|
108
108
|
}
|
|
109
109
|
static parse(json) {
|
|
110
|
-
return JSON.parse(json, (_, value) => value?.__Uint8Array ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
110
|
+
return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
class Codec {
|
|
@@ -318,6 +318,16 @@ class BaseTrait extends MsgTrait {
|
|
|
318
318
|
/* construct API class */
|
|
319
319
|
constructor(mqtt, options = {}) {
|
|
320
320
|
super(options);
|
|
321
|
+
if (mqtt === null)
|
|
322
|
+
mqtt = new Proxy({}, {
|
|
323
|
+
get(_target, prop, _receiver) {
|
|
324
|
+
if (prop === "isFakeProxy")
|
|
325
|
+
return true;
|
|
326
|
+
else
|
|
327
|
+
return (...args) => {
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
});
|
|
321
331
|
this.mqtt = mqtt;
|
|
322
332
|
this._messageHandler = (topic, message, packet) => {
|
|
323
333
|
this._onMessage(topic, message, packet);
|
|
@@ -418,11 +428,13 @@ class EventTrait extends BaseTrait {
|
|
|
418
428
|
let params;
|
|
419
429
|
let receiver;
|
|
420
430
|
let options = {};
|
|
431
|
+
let dry;
|
|
421
432
|
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
422
433
|
event = eventOrConfig.event;
|
|
423
434
|
params = eventOrConfig.params;
|
|
424
435
|
receiver = eventOrConfig.receiver;
|
|
425
436
|
options = eventOrConfig.options ?? {};
|
|
437
|
+
dry = eventOrConfig.dry;
|
|
426
438
|
} else {
|
|
427
439
|
event = eventOrConfig;
|
|
428
440
|
params = args;
|
|
@@ -431,7 +443,10 @@ class EventTrait extends BaseTrait {
|
|
|
431
443
|
const request = this.msg.makeEventEmission(rid, event, params, this.options.id, receiver);
|
|
432
444
|
const message = this.codec.encode(request);
|
|
433
445
|
const topic = this.options.topicMake(event, "event-emission", receiver);
|
|
434
|
-
|
|
446
|
+
if (dry)
|
|
447
|
+
return { topic, payload: Buffer$1.from(message), options: { qos: 0, ...options } };
|
|
448
|
+
else
|
|
449
|
+
this.mqtt.publish(topic, Buffer$1.from(message), { qos: 0, ...options });
|
|
435
450
|
}
|
|
436
451
|
/* dispatch message (Event pattern handling) */
|
|
437
452
|
_dispatchMessage(topic, parsed) {
|
|
@@ -552,8 +567,14 @@ class ServiceTrait extends EventTrait {
|
|
|
552
567
|
if (!this.responseSubscriptions.has(topic)) {
|
|
553
568
|
this.responseSubscriptions.set(topic, 0);
|
|
554
569
|
this.mqtt.subscribe(topic, options, (err) => {
|
|
555
|
-
if (err)
|
|
570
|
+
if (err) {
|
|
571
|
+
const count2 = this.responseSubscriptions.get(topic) ?? 0;
|
|
572
|
+
if (count2 > 1)
|
|
573
|
+
this.responseSubscriptions.set(topic, count2 - 1);
|
|
574
|
+
else
|
|
575
|
+
this.responseSubscriptions.delete(topic);
|
|
556
576
|
this.mqtt.emit("error", err);
|
|
577
|
+
}
|
|
557
578
|
});
|
|
558
579
|
}
|
|
559
580
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
@@ -565,8 +586,9 @@ class ServiceTrait extends EventTrait {
|
|
|
565
586
|
if (!this.responseSubscriptions.has(topic))
|
|
566
587
|
return;
|
|
567
588
|
const count = this.responseSubscriptions.get(topic) ?? 0;
|
|
568
|
-
|
|
569
|
-
|
|
589
|
+
if (count > 1)
|
|
590
|
+
this.responseSubscriptions.set(topic, count - 1);
|
|
591
|
+
else {
|
|
570
592
|
this.responseSubscriptions.delete(topic);
|
|
571
593
|
this.mqtt.unsubscribe(topic, (err) => {
|
|
572
594
|
if (err)
|