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 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
- /* publish message to MQTT topic */
103
- this.mqtt.publish(topic, Buffer.from(message), { qos: 0, ...options });
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
- this.responseSubscriptions.set(topic, count - 1);
167
- if (this.responseSubscriptions.get(topic) === 0) {
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
- this.mqtt.publish(topic, node_buffer.Buffer.from(message), { qos: 0, ...options });
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
- this.responseSubscriptions.set(topic, count - 1);
587
- if (this.responseSubscriptions.get(topic) === 0) {
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
- this.mqtt.publish(topic, Buffer$1.from(message), { qos: 0, ...options });
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
- this.responseSubscriptions.set(topic, count - 1);
569
- if (this.responseSubscriptions.get(topic) === 0) {
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)