mqtt-plus 0.9.17 → 1.0.0

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 CHANGED
@@ -2,6 +2,17 @@
2
2
  ChangeLog
3
3
  =========
4
4
 
5
+ 1.0.0 (2026-01-25)
6
+ ------------------
7
+
8
+ - CLEANUP: various code cleanups
9
+
10
+ 0.9.18 (2026-01-25)
11
+ -------------------
12
+
13
+ - IMPROVEMENT: support config parameter API variant also in subscribe/register/provision
14
+ - IMPROVEMENT: support MQTT 5 shared subscriptions also in subscribe/register/provision
15
+
5
16
  0.9.17 (2026-01-25)
6
17
  -------------------
7
18
 
package/README.md CHANGED
@@ -113,12 +113,16 @@ const mqttp = new MQTTp<API>(mqtt)
113
113
 
114
114
  mqtt.on("connect", async () => {
115
115
  await mqttp.subscribe("example/sample", (a1, a2, info) => {
116
- console.log("example/sample:", a1, a2, "from:", info.sender)
116
+ console.log("example/sample: SERVER:", a1, a2, info.sender)
117
117
  })
118
118
  await mqttp.register("example/hello", (a1, a2, info) => {
119
- console.log("example/hello:", a1, a2, "from:", info.sender)
119
+ console.log("example/hello: SERVER:", a1, a2, info.sender)
120
120
  return `${a1}:${a2}`
121
121
  })
122
+ await mqttp.provision("example/resource", async (filename, info) => {
123
+ console.log("example/resource: SERVER:", filename, info.sender)
124
+ info.buffer = Promise.resolve(new TextEncoder().encode(`the ${filename} content`))
125
+ })
122
126
  })
123
127
  ```
124
128
 
@@ -132,12 +136,17 @@ import type { API } from [...]
132
136
  const mqtt = MQTT.connect("wss://127.0.0.1:8883", { [...] })
133
137
  const mqttp = new MQTTp<API>(mqtt)
134
138
 
135
- mqtt.on("connect", () => {
139
+ mqtt.on("connect", async () => {
136
140
  mqttp.emit("example/sample", "world", 42)
137
- mqttp.call("example/hello", "world", 42).then((response) => {
138
- console.log("example/hello response:", response)
139
- mqtt.end()
140
- })
141
+
142
+ const response = await mqttp.call("example/hello", "world", 42)
143
+ console.log("example/hello CLIENT:", response)
144
+
145
+ const result = await mqttp.fetch("example/resource", "foo")
146
+ const data = new TextDecoder().decode(await result.buffer)
147
+ console.log("example/resource CLIENT:", data)
148
+
149
+ mqtt.end()
141
150
  })
142
151
  ```
143
152
 
@@ -148,8 +157,13 @@ The **MQTT+** API provides the following methods:
148
157
 
149
158
  - **Construction**:<br/>
150
159
 
151
- constructor<API>(
152
- mqtt: MqttClient,
160
+ /* (simplified TypeScript API method signature) */
161
+ constructor<API extends Record<string,
162
+ Event< (...args: any[]) => void | Promise<void>> |
163
+ Service< (...args: any[]) => any | Promise<any> > |
164
+ Resource<(...args: any[]) => void | Promise<void>>
165
+ >>(
166
+ mqtt: MqttClient | null,
153
167
  options?: {
154
168
  id: string
155
169
  codec: "cbor" | "json"
@@ -160,19 +174,16 @@ The **MQTT+** API provides the following methods:
160
174
  }
161
175
  )
162
176
 
163
- The `API` is an optional TypeScript type `Record<string, ((...args:
164
- any[]) => void) | ((...args: any[]) => any)>`, describing the
165
- available events and services. Callbacks without return values
166
- describe events, callbacks with return values describe services.
167
-
177
+ The `API` is an optional TypeScript type,
178
+ describing the available events, services and resources.
168
179
  The `mqtt` is the [MQTT.js](https://www.npmjs.com/package/mqtt) instance,
169
- which has to be established separately.
170
-
171
- Use `destroy()` to clean up when the instance is no longer needed.
180
+ which has to be established separately. A `null` MQTT instance can be
181
+ used for performing dry-runs (see *Dry-Run Publishing for MQTT Last-Will* under
182
+ **Event Emission** below).
172
183
 
173
184
  The optional `options` object supports the following fields:
174
185
  - `id`: Custom MQTT peer identifier (default: auto-generated NanoID).
175
- - `codec`: Encoding format (default: `cbor`).
186
+ - `codec`: Encoding format, either `cbor` or `json` (default: `cbor`).
176
187
  - `timeout`: Communication timeout in milliseconds (default: `10000`).
177
188
  - `chunkSize`: Chunk size in bytes for resource transfers (default: `16384`).
178
189
  - `topicMake`: Custom topic generation function.
@@ -195,18 +206,29 @@ The **MQTT+** API provides the following methods:
195
206
  /* (simplified TypeScript API method signature) */
196
207
  subscribe(
197
208
  event: string,
198
- options?: MQTT::IClientSubscribeOptions
199
209
  callback: (
200
210
  ...params: any[],
201
211
  info: { sender: string, receiver?: string }
202
212
  ) => void | Promise<void>
203
213
  ): Promise<Subscription>
214
+ subscribe({
215
+ event: string,
216
+ callback: (
217
+ ...params: any[],
218
+ info: { sender: string, receiver?: string }
219
+ ) => void | Promise<void>,
220
+ options?: MQTT::IClientSubscribeOptions,
221
+ share?: string
222
+ }): Promise<Subscription>
204
223
 
205
224
  Subscribe to an event.
206
225
  The `event` has to be a valid MQTT topic name.
207
- The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
208
226
  The `callback` is called with the `params` passed to a remote `emit()`.
209
227
  There is no return value of `callback`.
228
+ The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
229
+ The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
230
+ (MQTT 5.0) for load-balancing messages across multiple subscribers by specifying
231
+ a group name. This internally prefixes the event with `$share/<share>/`.
210
232
 
211
233
  Internally, on the MQTT broker, the topics generated by
212
234
  `topicMake(event, "event-emission")` (default: `${event}/event-emission/any` and
@@ -218,18 +240,29 @@ The **MQTT+** API provides the following methods:
218
240
  /* (simplified TypeScript API method signature) */
219
241
  register(
220
242
  service: string,
221
- options?: MQTT::IClientSubscribeOptions
222
243
  callback: (
223
244
  ...params: any[],
224
245
  info: { sender: string, receiver?: string }
225
246
  ) => any | Promise<any>
226
247
  ): Promise<Registration>
248
+ register({
249
+ service: string,
250
+ callback: (
251
+ ...params: any[],
252
+ info: { sender: string, receiver?: string }
253
+ ) => any | Promise<any>,
254
+ options?: MQTT::IClientSubscribeOptions,
255
+ share?: string
256
+ }): Promise<Registration>
227
257
 
228
258
  Register a service.
229
259
  The `service` has to be a valid MQTT topic name.
230
- The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
231
260
  The `callback` is called with the `params` passed to a remote `call()`.
232
261
  The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
262
+ The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
263
+ The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
264
+ (MQTT 5.0) for load-balancing service calls across multiple registrants by specifying
265
+ a group name. This internally prefixes the service with `$share/<share>/`.
233
266
 
234
267
  Internally, on the MQTT broker, the topics generated by
235
268
  `topicMake(service, "service-call-request")` (default: `${service}/service-call-request/any` and
@@ -241,30 +274,47 @@ The **MQTT+** API provides the following methods:
241
274
  /* (simplified TypeScript API method signature) */
242
275
  provision(
243
276
  resource: string,
244
- options?: MQTT::IClientSubscribeOptions
245
277
  callback: (
246
278
  ...params: any[],
247
279
  info: {
248
- sender: string,
280
+ sender: string,
249
281
  receiver?: string,
250
- meta?: Record<string, any>,
251
- stream?: Readable,
252
- buffer?: Promise<Buffer>
282
+ meta?: Record<string, any>,
283
+ stream?: Readable,
284
+ buffer?: Promise<Uint8Array>
253
285
  }
254
286
  ) => void | Promise<void>
255
287
  ): Promise<Provisioning>
288
+ provision({
289
+ resource: string,
290
+ callback: (
291
+ ...params: any[],
292
+ info: {
293
+ sender: string,
294
+ receiver?: string,
295
+ meta?: Record<string, any>,
296
+ stream?: Readable,
297
+ buffer?: Promise<Uint8Array>
298
+ }
299
+ ) => void | Promise<void>,
300
+ options?: MQTT::IClientSubscribeOptions,
301
+ share?: string
302
+ }): Promise<Provisioning>
256
303
 
257
304
  Provision a resource for both fetch requests and pushed data.
258
305
  The `resource` has to be a valid MQTT topic name.
259
306
  The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
307
+ The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
308
+ (MQTT 5.0) for load-balancing resource requests across multiple provisioners by specifying
309
+ a group name. This internally prefixes the resource with `$share/<share>/`.
260
310
 
261
311
  For **fetch requests**: The `callback` is called with the `params` passed to a remote `fetch()`.
262
- The `callback` should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Buffer>` containing the resource data.
312
+ The `callback` should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Uint8Array>` containing the resource data.
263
313
  Optionally, the `callback` can set `info.meta` to a `Record<string, any>` to send metadata back with the response.
264
314
 
265
315
  For **pushed data**: The `callback` is called with the `params` passed to a remote `push()`.
266
316
  The `info.stream` provides a Node.js `Readable` stream for consuming the pushed data.
267
- The `info.buffer` provides a lazy `Promise<Buffer>` that resolves to the complete data once the stream ends.
317
+ The `info.buffer` provides a lazy `Promise<Uint8Array>` that resolves to the complete data once the stream ends.
268
318
  The `info.meta` contains optional metadata sent by the pusher via `push()`.
269
319
 
270
320
  Internally, on the MQTT broker, the topics by
@@ -292,7 +342,7 @@ The **MQTT+** API provides the following methods:
292
342
  receiver?: string,
293
343
  options?: MQTT::IClientSubscribeOptions,
294
344
  dry: true
295
- }): { topic: string, payload: Buffer, options: IClientPublishOptions }
345
+ }): { topic: string, payload: Uint8Array, options: IClientPublishOptions }
296
346
 
297
347
  Emit an event to all subscribers or a specific subscriber ("fire and forget").
298
348
  The optional `receiver` directs the event to a specific subscriber only.
@@ -307,7 +357,7 @@ The **MQTT+** API provides the following methods:
307
357
  Internally, publishes to the MQTT topic by `topicMake(event, "event-emission", peerId)`
308
358
  (default: `${event}/event-emission/any` or `${event}/event-emission/${peerId}`).
309
359
 
310
- **Dry-Run Publishing for MQTT Last-Will:**
360
+ *Dry-Run Publishing for MQTT Last-Will:*
311
361
  When you need to set up an MQTT "last will" message (automatically published
312
362
  by the broker when a client disconnects *unexpectedly*), you can use `dry: true`
313
363
  together with a `null` MQTT client:
@@ -367,7 +417,7 @@ The **MQTT+** API provides the following methods:
367
417
  ...params: any[]
368
418
  ): Promise<{
369
419
  stream: Readable,
370
- buffer: Promise<Buffer>,
420
+ buffer: Promise<Uint8Array>,
371
421
  meta: Promise<Record<string, any> | undefined>
372
422
  }>
373
423
  fetch({
@@ -377,7 +427,7 @@ The **MQTT+** API provides the following methods:
377
427
  options?: MQTT::IClientSubscribeOptions
378
428
  }): Promise<{
379
429
  stream: Readable,
380
- buffer: Promise<Buffer>,
430
+ buffer: Promise<Uint8Array>,
381
431
  meta: Promise<Record<string, any> | undefined>
382
432
  }>
383
433
 
@@ -386,12 +436,12 @@ The **MQTT+** API provides the following methods:
386
436
  The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
387
437
 
388
438
  Returns an object with a `stream` (`Readable`) for consuming the transferred data,
389
- a lazy `buffer` (`Promise<Buffer>`) that resolves to the complete data once the stream ends,
439
+ a lazy `buffer` (`Promise<Uint8Array>`) that resolves to the complete data once the stream ends,
390
440
  and a `meta` (`Promise<Record<string, any> | undefined>`) that resolves to optional metadata
391
441
  sent by the provisioner when the first chunk arrives.
392
442
 
393
443
  The remote `provision()` `callback` is called with `params` and
394
- should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Buffer>` containing the resource data.
444
+ should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Uint8Array>` containing the resource data.
395
445
  Optionally, the `callback` can set `info.meta` to send metadata back with the response.
396
446
  If the remote `callback` throws an exception, this destroys the stream with the error.
397
447
 
@@ -405,12 +455,12 @@ The **MQTT+** API provides the following methods:
405
455
  /* (simplified TypeScript API method signature) */
406
456
  push(
407
457
  resource: string,
408
- streamOrBuffer: Readable | Buffer,
458
+ streamOrBuffer: Readable | Uint8Array,
409
459
  ...params: any[]
410
460
  ): Promise<void>
411
461
  push({
412
462
  resource: string,
413
- streamOrBuffer: Readable | Buffer,
463
+ streamOrBuffer: Readable | Uint8Array,
414
464
  params: any[]
415
465
  meta?: Record<string, any>,
416
466
  receiver?: string,
@@ -418,7 +468,7 @@ The **MQTT+** API provides the following methods:
418
468
  }): Promise<void>
419
469
 
420
470
  Pushes a resource to all provisioners or a specific provisioner.
421
- The `streamOrBuffer` is either a Node.js `Readable` stream or a `Buffer` providing the data to push.
471
+ The `streamOrBuffer` is either a Node.js `Readable` stream or a `Uint8Array` providing the data to push.
422
472
  The optional `meta` sends metadata alongside the resource data,
423
473
  which becomes available on the provisioner side via `info.meta`.
424
474
  The optional `receiver` directs the push to a specific provisioner only.
@@ -431,7 +481,7 @@ The **MQTT+** API provides the following methods:
431
481
 
432
482
  The remote `provision()` `callback` is called with `params` and an `info` object
433
483
  containing `stream` (`Readable`) for consuming the pushed data,
434
- `buffer` (lazy `Promise<Buffer>`) that resolves to the complete data once the stream ends,
484
+ `buffer` (lazy `Promise<Uint8Array>`) that resolves to the complete data once the stream ends,
435
485
  and `meta` (`Record<string, any> | undefined`) containing the metadata sent by the pusher.
436
486
 
437
487
  Internally, publishes to the MQTT topic by `topicMake(resource, "resource-transfer-response", peerId)`
@@ -446,8 +496,12 @@ In the following, we assume that an **MQTT+** instance is created with:
446
496
  import MQTT from "mqtt"
447
497
  import MQTTp from "mqtt-plus"
448
498
 
499
+ export type API = {
500
+ "example/sample": Event<(a1: string, a2: number) => void>
501
+ ...
502
+ }
449
503
  const mqtt = MQTT.connect("...", { ... })
450
- const mqttp = new MQTTp(mqtt, { codec: "json" })
504
+ const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
451
505
  ```
452
506
 
453
507
  Internally, remote services are assigned to MQTT topics. When calling a
@@ -501,78 +555,18 @@ The `sender` field is the NanoID of the MQTT+ sender instance and
501
555
  used for sending back the response message to the requestor only. The
502
556
  `id` is used for correlating the response to the request only.
503
557
 
504
- Broker Setup
505
- ------------
506
-
507
- For a real test-drive of MQTT+, install the
508
- [Mosquitto](https://mosquitto.org/) MQTT broker and a `mosquitto.conf`
509
- file like...
510
-
511
- ```
512
- [...]
513
-
514
- password_file mosquitto-pwd.txt
515
- acl_file mosquitto-acl.txt
516
-
517
- [...]
518
-
519
- # additional listener
520
- listener 1883 127.0.0.1
521
- max_connections -1
522
- protocol mqtt
523
-
524
- [...]
525
- ```
526
-
527
- ...and an access control list in `mosquitto-acl.txt` like...
528
-
529
- ```
530
- # shared ACL
531
- topic read $SYS/#
532
- pattern write $SYS/broker/connection/%c/state
533
- pattern readwrite %u/#
534
-
535
- # anonymous ACL
536
- topic read event/+
537
- pattern read event/+/%c
538
- topic write event/+/+
539
- pattern read service/+/%c
540
- topic write service/+
541
- topic write service/+/+
542
-
543
- # user ACL
544
- user example
545
- topic read event/+
546
- pattern read event/+/%c
547
- topic write event/+
548
- topic write event/+/+
549
- topic read service/+
550
- pattern read service/+/%c
551
- topic write service/+
552
- topic write service/+/+
553
- ```
554
-
555
- ...and an `example` user (with password `example`) in `mosquitto-pwd.txt` like:
556
-
557
- ```
558
- example:$6$awYNe6oCAi+xlvo5$mWIUqyy4I0O3nJ99lP1mkRVqsDGymF8en5NChQQxf7KrVJLUp1SzrrVDe94wWWJa3JGIbOXD9wfFGZdi948e6A==
559
- ```
560
-
561
- Alternatively, you can use the [NPM package mosquitto](https://npmjs.com/mosquitto)
562
- for an equal setup.
563
-
564
558
  Example
565
559
  -------
566
560
 
567
- You can test-drive MQTT+ with a complete [sample](sample/sample.ts) to see
568
- it in action and tracing its communication (the typing of the `MQTTp`
569
- class with `API` is optional, but strongly suggested):
561
+ You can test-drive MQTT+, in a temporary MQTT broker environment,
562
+ with a complete [sample](sample/sample.ts) to see it in action and
563
+ tracing its communication:
570
564
 
571
565
  ```ts
572
- import Mosquitto from "mosquitto"
573
- import MQTT from "mqtt"
574
- import MQTTp from "mqtt-plus"
575
- import type * as MQTTpt from "mqtt-plus"
566
+ import Mosquitto from "mosquitto"
567
+ import MQTT from "mqtt"
568
+ import MQTTp from "mqtt-plus"
569
+ import type { Event, Service, Resource } from "mqtt-plus"
576
570
 
577
571
  const mosquitto = new Mosquitto()
578
572
  await mosquitto.start()
@@ -584,9 +578,9 @@ const mqtt = MQTT.connect("mqtt://127.0.0.1:1883", {
584
578
  })
585
579
 
586
580
  type API = {
587
- "example/sample": MQTTpt.Event<(a1: string, a2: number) => void>
588
- "example/hello": MQTTpt.Service<(a1: string, a2: number) => string>
589
- "example/resource": MQTTpt.Resource<(filename: string) => void>
581
+ "example/sample": Event<(a1: string, a2: number) => void>
582
+ "example/hello": Service<(a1: string, a2: number) => string>
583
+ "example/resource": Resource<(filename: string) => void>
590
584
  }
591
585
 
592
586
  const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
@@ -620,11 +614,12 @@ mqtt.on("connect", async () => {
620
614
  /* resource fetch example */
621
615
  const prov = await mqttp.provision("example/resource", async (filename, info) => {
622
616
  console.log("example/resource: request:", filename, "from:", info.sender)
623
- info.buffer = Promise.resolve(Buffer.from(`the ${filename} content`))
617
+ const data = `the ${filename} content`
618
+ info.buffer = Promise.resolve(new TextEncoder().encode(data))
624
619
  })
625
620
  const res = await mqttp.fetch("example/resource", "foo")
626
- const data = await res.buffer
627
- console.log("example/resource: result:", data.toString())
621
+ const data = new TextDecoder().decode(await res.buffer)
622
+ console.log("example/resource: result:", data)
628
623
  await prov.unprovision()
629
624
 
630
625
  mqtt.end()
@@ -650,11 +645,68 @@ example/resource: result: the foo content
650
645
  CLOSE
651
646
  ```
652
647
 
648
+ Broker Setup
649
+ ------------
650
+
651
+ For establishing your own permanent MQTT environment, install the
652
+ [Mosquitto](https://mosquitto.org/) MQTT broker yourself and a setup
653
+ a `mosquitto.conf` file like...
654
+
655
+ ```
656
+ [...]
657
+
658
+ password_file mosquitto-pwd.txt
659
+ acl_file mosquitto-acl.txt
660
+
661
+ [...]
662
+
663
+ # additional listener
664
+ listener 1883 127.0.0.1
665
+ max_connections -1
666
+ protocol mqtt
667
+
668
+ [...]
669
+ ```
670
+
671
+ ...and an access control list in `mosquitto-acl.txt` like...
672
+
673
+ ```
674
+ # shared ACL
675
+ topic read $SYS/#
676
+ pattern write $SYS/broker/connection/%c/state
677
+ pattern readwrite %u/#
678
+
679
+ # anonymous ACL
680
+ topic read event/+
681
+ pattern read event/+/%c
682
+ topic write event/+/+
683
+ pattern read service/+/%c
684
+ topic write service/+
685
+ topic write service/+/+
686
+
687
+ # user ACL
688
+ user example
689
+ topic read event/+
690
+ pattern read event/+/%c
691
+ topic write event/+
692
+ topic write event/+/+
693
+ topic read service/+
694
+ pattern read service/+/%c
695
+ topic write service/+
696
+ topic write service/+/+
697
+ ```
698
+
699
+ ...and an `example` user (with password `example`) in `mosquitto-pwd.txt` like:
700
+
701
+ ```
702
+ example:$6$awYNe6oCAi+xlvo5$mWIUqyy4I0O3nJ99lP1mkRVqsDGymF8en5NChQQxf7KrVJLUp1SzrrVDe94wWWJa3JGIbOXD9wfFGZdi948e6A==
703
+ ```
704
+
653
705
  Notice
654
706
  ------
655
707
 
656
708
  > [!Note]
657
- > **MQTT+** is somewhat similar to and originally derived from
709
+ > **MQTT+** is somewhat similar to and originally derived from the weaker
658
710
  > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
659
711
  > author, but instead of just JSON, MQTT+ encodes packets as JSON
660
712
  > or CBOR (default), uses an own packet format (allowing sender and
@@ -35,7 +35,7 @@ export class BaseTrait extends MsgTrait {
35
35
  if (prop === "isFakeProxy")
36
36
  return true;
37
37
  else
38
- return (...args) => { };
38
+ return () => { };
39
39
  }
40
40
  });
41
41
  /* store MQTT client */
@@ -86,9 +86,7 @@ export default class Codec {
86
86
  }
87
87
  decode(data) {
88
88
  let result;
89
- if (this.type === "cbor"
90
- && typeof data === "object"
91
- && data instanceof Uint8Array) {
89
+ if (this.type === "cbor" && data instanceof Uint8Array) {
92
90
  try {
93
91
  result = CBOR.decode(data, { tags: this.tags });
94
92
  }
@@ -9,7 +9,12 @@ export interface Subscription {
9
9
  export declare class EventTrait<T extends APISchema = APISchema> extends BaseTrait<T> {
10
10
  private subscriptions;
11
11
  subscribe<K extends EventKeys<T> & string>(event: K, callback: WithInfo<T[K], InfoEvent>): Promise<Subscription>;
12
- subscribe<K extends EventKeys<T> & string>(event: K, options: Partial<IClientSubscribeOptions>, callback: WithInfo<T[K], InfoEvent>): Promise<Subscription>;
12
+ subscribe<K extends EventKeys<T> & string>(config: {
13
+ event: K;
14
+ callback: WithInfo<T[K], InfoEvent>;
15
+ options?: Partial<IClientSubscribeOptions>;
16
+ share?: string;
17
+ }): Promise<Subscription>;
13
18
  emit<K extends EventKeys<T> & string>(event: K, ...params: Parameters<T[K]>): void;
14
19
  emit<K extends EventKeys<T> & string>(config: {
15
20
  event: K;
@@ -34,20 +34,31 @@ export class EventTrait extends BaseTrait {
34
34
  /* internal state */
35
35
  this.subscriptions = new Map();
36
36
  }
37
- async subscribe(event, ...args) {
38
- /* determine parameters */
37
+ async subscribe(eventOrConfig, ...args) {
38
+ /* determine actual parameters */
39
+ let event;
40
+ let callback;
39
41
  let options = {};
40
- let callback = args[0];
41
- if (args.length === 2 && typeof args[0] === "object") {
42
- options = args[0];
43
- callback = args[1];
42
+ let share;
43
+ if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
44
+ /* object-based API */
45
+ event = eventOrConfig.event;
46
+ callback = eventOrConfig.callback;
47
+ options = eventOrConfig.options ?? {};
48
+ share = eventOrConfig.share;
49
+ }
50
+ else {
51
+ /* positional API */
52
+ event = eventOrConfig;
53
+ callback = args[0];
44
54
  }
45
55
  /* sanity check situation */
46
56
  if (this.subscriptions.has(event))
47
57
  throw new Error(`subscribe: event "${event}" already subscribed`);
48
58
  /* generate the corresponding MQTT topics for broadcast and direct use */
49
- const topicB = this.options.topicMake(event, "event-emission");
50
- const topicD = this.options.topicMake(event, "event-emission", this.options.id);
59
+ const name = share ? `$share/${share}/${event}` : event;
60
+ const topicB = this.options.topicMake(name, "event-emission");
61
+ const topicD = this.options.topicMake(name, "event-emission", this.options.id);
51
62
  /* subscribe to MQTT topics */
52
63
  await Promise.all([
53
64
  this._subscribeTopic(topicB, { qos: 0, ...options }),
@@ -1,11 +1,12 @@
1
1
  import { APISchema } from "./mqtt-plus-api";
2
2
  import { CodecTrait } from "./mqtt-plus-codec";
3
+ type MessageType = "event-emission" | "service-call-request" | "service-call-response" | "resource-transfer-request" | "resource-transfer-response";
3
4
  declare class Base {
4
- type: string;
5
+ type: MessageType;
5
6
  id: string;
6
7
  sender?: string | undefined;
7
8
  receiver?: string | undefined;
8
- constructor(type: string, id: string, sender?: string | undefined, receiver?: string | undefined);
9
+ constructor(type: MessageType, id: string, sender?: string | undefined, receiver?: string | undefined);
9
10
  }
10
11
  export declare class EventEmission extends Base {
11
12
  event: string;
@@ -153,7 +153,7 @@ class Msg {
153
153
  else if (obj.type === "resource-transfer-response") {
154
154
  if (obj.resource !== undefined && typeof obj.resource !== "string")
155
155
  throw new Error("invalid ResourceTransferResponse object: \"resource\" field must be a string");
156
- if (obj.chunk !== undefined && typeof obj.chunk !== "object")
156
+ if (obj.chunk !== undefined && (obj.chunk === null || typeof obj.chunk !== "object"))
157
157
  throw new Error("invalid ResourceTransferResponse object: \"chunk\" field must be an object");
158
158
  if (obj.meta !== undefined && (typeof obj.meta !== "object" || obj.meta === null || Array.isArray(obj.meta)))
159
159
  throw new Error("invalid ResourceTransferResponse object: \"meta\" field must be an object");
@@ -12,7 +12,12 @@ export declare class ResourceTrait<T extends APISchema = APISchema> extends Serv
12
12
  private pushStreams;
13
13
  private pushTimers;
14
14
  provision<K extends ResourceKeys<T> & string>(resource: K, callback: WithInfo<T[K], InfoResource>): Promise<Provisioning>;
15
- provision<K extends ResourceKeys<T> & string>(resource: K, options: Partial<IClientSubscribeOptions>, callback: WithInfo<T[K], InfoResource>): Promise<Provisioning>;
15
+ provision<K extends ResourceKeys<T> & string>(config: {
16
+ resource: K;
17
+ callback: WithInfo<T[K], InfoResource>;
18
+ options?: Partial<IClientSubscribeOptions>;
19
+ share?: string;
20
+ }): Promise<Provisioning>;
16
21
  push<K extends ResourceKeys<T> & string>(resource: K, data: Readable | Uint8Array, ...params: Parameters<T[K]>): Promise<void>;
17
22
  push<K extends ResourceKeys<T> & string>(config: {
18
23
  resource: K;